feat(auth): Make token auth default (#6305)

**What**
- make token auth the default being returned from authentication endpoints in api-v2
- Add `auth/session` to convert token to session based auth
- add regex-scopes to authenticate middleware 

Co-authored-by: Sebastian Rindom <7554214+srindom@users.noreply.github.com>
This commit is contained in:
Philip KorsholmandSebastian Rindom authored and GitHub committed 2024-02-05 08:17:08 +00:00
1 parent 96ba49329b
commit e2738ab91d
21 files changed
+147 -138

No files matched your search

+3 -3
View File
@@ -1,8 +1,8 @@
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
import {
AuthProviderScope,
AuthenticationInput,
AuthenticationResponse,
AuthProviderScope,
ModulesSdkTypes,
} from "@medusajs/types"
import { AuthUserService } from "@services"
@@ -82,9 +82,9 @@ class GoogleProvider extends AbstractAuthModuleProvider {
// abstractable
async verify_(refreshToken: string, scope: string) {
const jwtData = (await jwt.decode(refreshToken, {
const jwtData = jwt.decode(refreshToken, {
complete: true,
})) as JwtPayload
}) as JwtPayload
const entity_id = jwtData.payload.email
let authUser
-55
View File
@@ -1,5 +1,3 @@
import jwt from "jsonwebtoken"
import {
AuthenticationInput,
AuthenticationResponse,
@@ -11,7 +9,6 @@ import {
CreateAuthUserDTO,
DAL,
InternalModuleDeclaration,
JWTGenerationOptions,
ModuleJoinerConfig,
ModulesSdkTypes,
UpdateAuthUserDTO,
@@ -31,15 +28,6 @@ import {
} from "@medusajs/utils"
import { ServiceTypes } from "@types"
type AuthModuleOptions = {
jwt_secret: string
}
type AuthJWTPayload = {
id: string
scope: string
}
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
authUserService: ModulesSdkTypes.InternalModuleService<any>
@@ -68,7 +56,6 @@ export default class AuthModuleService<
protected baseRepository_: DAL.RepositoryService
protected authUserService_: ModulesSdkTypes.InternalModuleService<TAuthUser>
protected authProviderService_: ModulesSdkTypes.InternalModuleService<TAuthProvider>
protected options_: AuthModuleOptions
constructor(
{
@@ -76,7 +63,6 @@ export default class AuthModuleService<
authProviderService,
baseRepository,
}: InjectedDependencies,
options: AuthModuleOptions,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
// @ts-ignore
@@ -85,53 +71,12 @@ export default class AuthModuleService<
this.baseRepository_ = baseRepository
this.authUserService_ = authUserService
this.authProviderService_ = authProviderService
this.options_ = options
}
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
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
@@ -1,8 +1,8 @@
import { AuthenticationInput, IAuthModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
import jwt from "jsonwebtoken"
import { MedusaError } from "@medusajs/utils"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { AuthenticationInput, IAuthModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { scope, authProvider } = req.params
@@ -29,10 +29,9 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
}
if (success) {
req.session.auth_user = authUser
req.session.scope = authUser.scope
return res.status(200).json({ authUser })
const { jwt_secret } = req.scope.resolve("configModule").projectConfig
const token = jwt.sign(authUser, jwt_secret)
return res.status(200).json({ token })
}
throw new MedusaError(
@@ -1,8 +1,8 @@
import jwt from "jsonwebtoken"
import { AuthenticationInput, IAuthModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import { MedusaError } from "@medusajs/utils"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { scope, authProvider } = req.params
@@ -29,10 +29,11 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
}
if (success) {
req.session.auth_user = authUser
req.session.scope = authUser.scope
const { jwt_secret } = req.scope.resolve("configModule").projectConfig
return res.status(200).json({ authUser })
const token = jwt.sign(authUser, jwt_secret)
return res.status(200).json({ token })
}
throw new MedusaError(
@@ -0,0 +1,10 @@
import { MiddlewareRoute } from "../../types/middlewares"
import { authenticate } from "../../utils/authenticate-middleware"
export const authRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
matcher: "/auth/session",
middlewares: [authenticate(/.*/, "bearer")],
},
]
@@ -0,0 +1,7 @@
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
req.session.auth_user = req.auth_user
res.status(200).json({ user: req.auth_user })
}
@@ -5,6 +5,7 @@ import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
import { authRoutesMiddlewares } from "./auth/middlewares"
export const config: MiddlewaresConfig = {
routes: [
@@ -14,5 +15,6 @@ export const config: MiddlewaresConfig = {
...adminCampaignRoutesMiddlewares,
...storeCustomerRoutesMiddlewares,
...storeCartRoutesMiddlewares,
...authRoutesMiddlewares,
],
}
+2
View File
@@ -228,9 +228,11 @@ class InviteService extends TransactionBaseService {
verifyToken(token): JwtPayload | string {
const { jwt_secret } = this.configModule_.projectConfig
if (jwt_secret) {
return jwt.verify(token, jwt_secret)
}
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Please configure jwt_secret"
-2
View File
@@ -1,7 +1,5 @@
import type { Customer, User } from "../models"
import type { NextFunction, Request, Response } from "express"
import { AuthUserDTO } from "@medusajs/types"
import type { MedusaContainer } from "./global"
export interface MedusaRequest extends Request {
@@ -1,8 +1,9 @@
import { AuthUserDTO, IAuthModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../types/routing"
import { NextFunction, RequestHandler } from "express"
import jwt, { JwtPayload } from "jsonwebtoken"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { AuthUserDTO } from "@medusajs/types"
import { stringEqualsOrRegexMatch } from "@medusajs/utils"
const SESSION_AUTH = "session"
const BEARER_AUTH = "bearer"
@@ -15,7 +16,7 @@ type MedusaSession = {
type AuthType = "session" | "bearer"
export const authenticate = (
authScope: string,
authScope: string | RegExp,
authType: AuthType | AuthType[],
options: { allowUnauthenticated?: boolean } = {}
): RequestHandler => {
@@ -25,22 +26,23 @@ export const authenticate = (
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_user && session.scope === authScope) {
if (
session.auth_user &&
stringEqualsOrRegexMatch(authScope, session.auth_user.scope)
) {
authUser = session.auth_user
}
}
if (!authUser && authTypes.includes(BEARER_AUTH)) {
const authHeader = req.headers.authorization
if (authHeader) {
const re = /(\S+)\s+(\S+)/
const matches = authHeader.match(re)
@@ -49,10 +51,17 @@ export const authenticate = (
if (matches) {
const tokenType = matches[1]
const token = matches[2]
if (tokenType.toLowerCase() === "bearer") {
authUser = await authModule
.retrieveAuthUserFromJwtToken(token, authScope)
.catch(() => null)
if (tokenType.toLowerCase() === BEARER_AUTH) {
// get config jwt secret
// verify token and set authUser
const { jwt_secret } =
req.scope.resolve("configModule").projectConfig
const verified = jwt.verify(token, jwt_secret) as JwtPayload
if (stringEqualsOrRegexMatch(authScope, verified.scope)) {
authUser = verified as AuthUserDTO
}
}
}
}
@@ -62,7 +71,7 @@ export const authenticate = (
req.auth_user = {
id: authUser.id,
app_metadata: authUser.app_metadata,
scope: authScope,
scope: authUser.scope,
}
return next()
}
-10
View File
@@ -76,16 +76,6 @@ 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>,
+1 -1
View File
@@ -34,6 +34,7 @@ export * from "./remove-nullisih"
export * from "./selector-constraints-to-string"
export * from "./set-metadata"
export * from "./simple-hash"
export * from "./string-or-regex-equals"
export * from "./string-to-select-relation-object"
export * from "./stringify-circular"
export * from "./to-camel-case"
@@ -42,4 +43,3 @@ export * from "./to-pascal-case"
export * from "./transaction"
export * from "./upper-case-first"
export * from "./wrap-handler"
@@ -0,0 +1,9 @@
export const stringEqualsOrRegexMatch = (
stringOrRegex: string | RegExp,
testString: string
) => {
if (stringOrRegex instanceof RegExp) {
return stringOrRegex.test(testString)
}
return stringOrRegex === testString
}