Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth, medusa): Initial auth module middleware #6271

Merged
merged 4 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 72 additions & 32 deletions packages/auth/src/services/auth-module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import jwt from "jsonwebtoken"

import {
AuthenticationInput,
AuthenticationResponse,
Expand Down Expand Up @@ -33,6 +35,15 @@ import {
} from "@medusajs/types"
import { ServiceTypes } from "@types"

type AuthModuleOptions = {
jwt_secret: string
}

type AuthJWTPayload = {
id: string
scope: string
}

type InjectedDependencies = {
baseRepository: DAL.RepositoryService
authUserService: AuthUserService<any>
Expand All @@ -57,19 +68,22 @@ export default class AuthModuleService<

protected authUserService_: AuthUserService<TAuthUser>
protected authProviderService_: AuthProviderService<TAuthProvider>
protected options_: AuthModuleOptions

constructor(
{
authUserService,
authProviderService,
baseRepository,
}: InjectedDependencies,
options: AuthModuleOptions,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
this.__container__ = arguments[0]
this.baseRepository_ = baseRepository
this.authUserService_ = authUserService
this.authProviderService_ = authProviderService
this.options_ = options
}

async retrieveAuthProvider(
Expand Down Expand Up @@ -100,9 +114,10 @@ export default class AuthModuleService<
sharedContext
)

return await this.baseRepository_.serialize<
AuthTypes.AuthProviderDTO[]
>(authProviders, { populate: true })
return await this.baseRepository_.serialize<AuthTypes.AuthProviderDTO[]>(
authProviders,
{ populate: true }
)
}

@InjectManager("baseRepository_")
Expand All @@ -118,13 +133,50 @@ export default class AuthModuleService<
)

return [
await this.baseRepository_.serialize<
AuthTypes.AuthProviderDTO[]
>(authProviders, { populate: true }),
await this.baseRepository_.serialize<AuthTypes.AuthProviderDTO[]>(
authProviders,
{ populate: true }
),
count,
]
}

async generateJwtToken(authUserId: string, scope: string): Promise<string> {
const authUser = await this.authUserService_.retrieve(authUserId)
return jwt.sign({ id: authUser.id, scope }, this.options_.jwt_secret, {
expiresIn: "1d",
srindom marked this conversation as resolved.
Show resolved Hide resolved
})
}

async retrieveAuthUserFromJwtToken(
token: string,
scope: string
): Promise<AuthUserDTO> {
let decoded: AuthJWTPayload
try {
const verifiedToken = jwt.verify(token, this.options_.jwt_secret)
decoded = verifiedToken as AuthJWTPayload
} catch (err) {
throw new MedusaError(
MedusaError.Types.UNAUTHORIZED,
"The provided JWT token is invalid"
)
}

if (decoded.scope !== scope) {
throw new MedusaError(
MedusaError.Types.UNAUTHORIZED,
"The provided JWT token is invalid"
)
}

const authUser = await this.authUserService_.retrieve(decoded.id)
return await this.baseRepository_.serialize<AuthTypes.AuthUserDTO>(
authUser,
{ populate: true }
)
}

async createAuthProvider(
data: CreateAuthProviderDTO[],
sharedContext?: Context
Expand All @@ -139,9 +191,7 @@ export default class AuthModuleService<
async createAuthProvider(
data: CreateAuthProviderDTO | CreateAuthProviderDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthProviderDTO | AuthTypes.AuthProviderDTO[]
> {
): Promise<AuthTypes.AuthProviderDTO | AuthTypes.AuthProviderDTO[]> {
const input = Array.isArray(data) ? data : [data]

const providers = await this.createAuthProviders_(input, sharedContext)
Expand Down Expand Up @@ -174,13 +224,9 @@ export default class AuthModuleService<

@InjectManager("baseRepository_")
async updateAuthProvider(
data:
| AuthTypes.UpdateAuthProviderDTO[]
| AuthTypes.UpdateAuthProviderDTO,
data: AuthTypes.UpdateAuthProviderDTO[] | AuthTypes.UpdateAuthProviderDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthProviderDTO | AuthTypes.AuthProviderDTO[]
> {
): Promise<AuthTypes.AuthProviderDTO | AuthTypes.AuthProviderDTO[]> {
const input = Array.isArray(data) ? data : [data]

const providers = await this.updateAuthProvider_(input, sharedContext)
Expand Down Expand Up @@ -241,11 +287,12 @@ export default class AuthModuleService<
sharedContext
)

return await this.baseRepository_.serialize<
AuthTypes.AuthUserDTO[]
>(authUsers, {
populate: true,
})
return await this.baseRepository_.serialize<AuthTypes.AuthUserDTO[]>(
authUsers,
{
populate: true,
}
)
}

@InjectManager("baseRepository_")
Expand All @@ -261,12 +308,9 @@ export default class AuthModuleService<
)

return [
await this.baseRepository_.serialize<AuthTypes.AuthUserDTO[]>(
authUsers,
{
populate: true,
}
),
await this.baseRepository_.serialize<AuthTypes.AuthUserDTO[]>(authUsers, {
populate: true,
}),
count,
]
}
Expand All @@ -284,9 +328,7 @@ export default class AuthModuleService<
async createAuthUser(
data: CreateAuthUserDTO[] | CreateAuthUserDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]
> {
): Promise<AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]> {
const input = Array.isArray(data) ? data : [data]

const authUsers = await this.createAuthUsers_(input, sharedContext)
Expand Down Expand Up @@ -321,9 +363,7 @@ export default class AuthModuleService<
async updateAuthUser(
data: UpdateAuthUserDTO | UpdateAuthUserDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]
> {
): Promise<AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]> {
const input = Array.isArray(data) ? data : [data]

const updatedUsers = await this.updateAuthUsers_(input, sharedContext)
Expand Down
1 change: 1 addition & 0 deletions packages/medusa/src/types/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { MedusaContainer } from "./global"
export interface MedusaRequest extends Request {
user?: (User | Customer) & { customer_id?: string; userId?: string }
scope: MedusaContainer
auth_user?: { id: string; app_metadata: Record<string, any>; scope: string }
}

export type MedusaResponse = Response
Expand Down
79 changes: 79 additions & 0 deletions packages/medusa/src/utils/authenticate-middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IAuthModuleService } from "@medusajs/types"
import { NextFunction, RequestHandler } from "express"
import { MedusaRequest, MedusaResponse } from "../types/routing"

type MedusaSession = {
auth: {
[authScope: string]: {
user_id: string
}
}
}

type AuthType = "session" | "bearer"

export default (
authScope: string,
authType: AuthType | AuthType[],
options: { allowUnauthenticated?: boolean } = {}
): RequestHandler => {
return async (
req: MedusaRequest,
res: MedusaResponse,
next: NextFunction
): Promise<void> => {
const authTypes = Array.isArray(authType) ? authType : [authType]
const authModule = req.scope.resolve<IAuthModuleService>(
ModuleRegistrationName.AUTH
)

// @ts-ignore
const session: MedusaSession = req.session || {}

if (authTypes.includes("session")) {
srindom marked this conversation as resolved.
Show resolved Hide resolved
if (session.auth && session.auth[authScope]) {
const authUser = await authModule.retrieveAuthUser(
session.auth[authScope].user_id
)
srindom marked this conversation as resolved.
Show resolved Hide resolved
req.auth_user = {
id: authUser.id,
app_metadata: authUser.app_metadata,
scope: authScope,
}
return next()
}
}

if (authTypes.includes("bearer")) {
const authHeader = req.headers.authorization
if (authHeader) {
const re = /(\S+)\s+(\S+)/
const matches = authHeader.match(re)

if (matches) {
const tokenType = matches[1]
const token = matches[2]
if (tokenType.toLowerCase() === "bearer") {
const authUser = await authModule.retrieveAuthUserFromJwtToken(
token,
authScope
)
srindom marked this conversation as resolved.
Show resolved Hide resolved
req.auth_user = {
id: authUser.id,
app_metadata: authUser.app_metadata,
scope: authScope,
}
return next()
}
}
}
}

if (options.allowUnauthenticated) {
return next()
}

res.status(401).json({ message: "Unauthorized" })
}
}
6 changes: 6 additions & 0 deletions packages/types/src/auth/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export interface IAuthModuleService extends IModuleService {
sharedContext?: Context
): Promise<AuthUserDTO>

generateJwtToken(authUserId: string, scope: string): Promise<string>
retrieveAuthUserFromJwtToken(
token: string,
scope: string
): Promise<AuthUserDTO>

listAuthUsers(
filters?: FilterableAuthProviderProps,
config?: FindConfig<AuthUserDTO>,
Expand Down
Loading