feat(auth, medusa): Initial auth module middleware (#6271)
note: This is an initial implementation Co-authored-by: Sebastian Rindom <7554214+srindom@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
import {
|
||||
AuthenticationInput,
|
||||
AuthenticationResponse,
|
||||
@@ -8,6 +10,7 @@ import {
|
||||
InternalModuleDeclaration,
|
||||
MedusaContainer,
|
||||
ModuleJoinerConfig,
|
||||
JWTGenerationOptions,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { AuthProvider, AuthUser } from "@models"
|
||||
@@ -33,6 +36,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>
|
||||
@@ -57,6 +69,7 @@ export default class AuthModuleService<
|
||||
|
||||
protected authUserService_: AuthUserService<TAuthUser>
|
||||
protected authProviderService_: AuthProviderService<TAuthProvider>
|
||||
protected options_: AuthModuleOptions
|
||||
|
||||
constructor(
|
||||
{
|
||||
@@ -64,12 +77,14 @@ export default class AuthModuleService<
|
||||
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(
|
||||
@@ -100,9 +115,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_")
|
||||
@@ -118,13 +134,54 @@ 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,
|
||||
options: JWTGenerationOptions = {}
|
||||
): Promise<string> {
|
||||
const authUser = await this.authUserService_.retrieve(authUserId)
|
||||
return jwt.sign({ id: authUser.id, scope }, this.options_.jwt_secret, {
|
||||
expiresIn: options.expiresIn || "1d",
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
@@ -139,9 +196,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)
|
||||
@@ -174,13 +229,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)
|
||||
@@ -241,11 +292,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_")
|
||||
@@ -261,12 +313,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,
|
||||
]
|
||||
}
|
||||
@@ -284,9 +333,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)
|
||||
@@ -321,9 +368,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
79
packages/medusa/src/utils/authenticate-middleware.ts
Normal file
79
packages/medusa/src/utils/authenticate-middleware.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { AuthUserDTO, IAuthModuleService } from "@medusajs/types"
|
||||
import { NextFunction, RequestHandler } from "express"
|
||||
import { MedusaRequest, MedusaResponse } from "../types/routing"
|
||||
|
||||
const SESSION_AUTH = "session"
|
||||
const BEARER_AUTH = "bearer"
|
||||
|
||||
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 || {}
|
||||
|
||||
let authUser: AuthUserDTO | null = null
|
||||
if (authTypes.includes(SESSION_AUTH)) {
|
||||
if (session.auth && session.auth[authScope]) {
|
||||
authUser = await authModule
|
||||
.retrieveAuthUser(session.auth[authScope].user_id)
|
||||
.catch(() => null)
|
||||
}
|
||||
}
|
||||
|
||||
if (authTypes.includes(BEARER_AUTH)) {
|
||||
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") {
|
||||
authUser = await authModule
|
||||
.retrieveAuthUserFromJwtToken(token, authScope)
|
||||
.catch(() => null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (authUser) {
|
||||
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" })
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,10 @@ import { Context } from "../shared-context"
|
||||
import { FindConfig } from "../common"
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
|
||||
export type JWTGenerationOptions = {
|
||||
expiresIn?: string | number
|
||||
}
|
||||
|
||||
export interface IAuthModuleService extends IModuleService {
|
||||
authenticate(
|
||||
provider: string,
|
||||
@@ -72,6 +76,16 @@ export interface IAuthModuleService extends IModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<AuthUserDTO>
|
||||
|
||||
generateJwtToken(
|
||||
authUserId: string,
|
||||
scope: string,
|
||||
options?: JWTGenerationOptions
|
||||
): Promise<string>
|
||||
retrieveAuthUserFromJwtToken(
|
||||
token: string,
|
||||
scope: string
|
||||
): Promise<AuthUserDTO>
|
||||
|
||||
listAuthUsers(
|
||||
filters?: FilterableAuthProviderProps,
|
||||
config?: FindConfig<AuthUserDTO>,
|
||||
|
||||
Reference in New Issue
Block a user