feat: Add successRedirectUrl to auth options (#6792)

This commit is contained in:
Oli Juhl authored and GitHub committed 2024-03-25 08:55:21 +01:00
1 parent aa154665de
commit 0deb2776ad
11 files changed
+147 -87

No files matched your search

@@ -14,3 +14,18 @@ Object {
"updated_at": Any<String>, "updated_at": Any<String>,
} }
`; `;
exports[`creates admin session correctly 1`] = `
Object {
"api_token": "test_token",
"created_at": Any<String>,
"deleted_at": null,
"email": "admin@medusa.js",
"first_name": null,
"id": "admin_user",
"last_name": null,
"metadata": null,
"role": "admin",
"updated_at": Any<String>,
}
`;
+68 -56
View File
@@ -1,66 +1,78 @@
const path = require("path")
const { Region, DiscountRule, Discount } = require("@medusajs/medusa")
const setupServer = require("../../../environment-helpers/setup-server")
const { useApi } = require("../../../environment-helpers/use-api") const { useApi } = require("../../../environment-helpers/use-api")
const { initDb, useDb } = require("../../../environment-helpers/use-db") const { medusaIntegrationTestRunner } = require("medusa-test-utils")
const adminSeeder = require("../../../helpers/admin-seeder") const { createAdminUser } = require("../../../helpers/create-admin-user")
const { exportAllDeclaration } = require("@babel/types") const { breaking } = require("../../../helpers/breaking")
const adminHeaders = {
headers: {
"x-medusa-access-token": "test_token",
},
}
jest.setTimeout(30000) jest.setTimeout(30000)
describe("/admin/auth", () => { medusaIntegrationTestRunner({
let medusaProcess env: { MEDUSA_FF_MEDUSA_V2: true },
let dbConnection testSuite: ({ dbConnection, getContainer, api }) => {
let container
beforeAll(async () => { beforeEach(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..")) container = getContainer()
dbConnection = await initDb({ cwd }) await createAdminUser(dbConnection, adminHeaders, container)
medusaProcess = await setupServer({ cwd })
await adminSeeder(dbConnection)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
it("creates admin session correctly", async () => {
const api = useApi()
const response = await api
.post("/admin/auth", {
email: "admin@medusa.js",
password: "secret_password",
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.user.password_hash).toEqual(undefined)
expect(response.data.user).toMatchSnapshot({
email: "admin@medusa.js",
created_at: expect.any(String),
updated_at: expect.any(String),
}) })
})
it("creates admin JWT token correctly", async () => { it("creates admin session correctly", async () => {
const api = useApi() const response = await breaking(
async () => {
return await api.post("/admin/auth", {
email: "admin@medusa.js",
password: "secret_password",
})
},
async () => {
return await api.post("/auth/admin/emailpass", {
email: "admin@medusa.js",
password: "secret_password",
})
}
)
const response = await api expect(response.status).toEqual(200)
.post("/admin/auth/token", {
email: "admin@medusa.js", const v1Result = {
password: "secret_password", user: expect.objectContaining({
email: "admin@medusa.js",
created_at: expect.any(String),
updated_at: expect.any(String),
}),
}
// In V2, we respond with a token instead of the user object on session creation
const v2Result = { token: expect.any(String) }
expect(response.data).toEqual(
breaking(
() => v1Result,
() => v2Result
)
)
})
// TODO: Remove in V2, as this is no longer supported
it("creates admin JWT token correctly", async () => {
breaking(async () => {
const response = await api
.post("/admin/auth/token", {
email: "admin@medusa.js",
password: "secret_password",
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.access_token).toEqual(expect.any(String))
}) })
.catch((err) => { })
console.log(err) },
})
expect(response.status).toEqual(200)
expect(response.data.access_token).toEqual(expect.any(String))
})
}) })
+1 -1
View File
@@ -5,7 +5,7 @@
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"test:integration": "jest --silent --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage", "test:integration": "jest --silent=false --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage",
"test:integration:chunk": "jest --silent --bail --maxWorkers=50% --forceExit --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")", "test:integration:chunk": "jest --silent --bail --maxWorkers=50% --forceExit --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")",
"build": "babel src -d dist --extensions \".ts,.js\"" "build": "babel src -d dist --extensions \".ts,.js\""
}, },
+4 -2
View File
@@ -4,10 +4,10 @@ const { User } = require("@medusajs/medusa/dist/models/user")
module.exports = async (dataSource, data = {}) => { module.exports = async (dataSource, data = {}) => {
const manager = dataSource.manager const manager = dataSource.manager
const buf = await Scrypt.kdf("secret_password", { logN: 1, r: 1, p: 1 }) const buf = await Scrypt.kdf("secret_password", { logN: 15, r: 8, p: 1 })
const password_hash = buf.toString("base64") const password_hash = buf.toString("base64")
await manager.insert(User, { const user = await manager.insert(User, {
id: "admin_user", id: "admin_user",
email: "admin@medusa.js", email: "admin@medusa.js",
api_token: "test_token", api_token: "test_token",
@@ -15,4 +15,6 @@ module.exports = async (dataSource, data = {}) => {
password_hash, password_hash,
...data, ...data,
}) })
return { user, password_hash }
} }
@@ -13,7 +13,7 @@ export const createAdminUser = async (
adminHeaders, adminHeaders,
container? container?
) => { ) => {
await adminSeeder(dbConnection) const { password_hash } = await adminSeeder(dbConnection)
const appContainer = container ?? getContainer()! const appContainer = container ?? getContainer()!
const authModule: IAuthModuleService = appContainer.resolve( const authModule: IAuthModuleService = appContainer.resolve(
@@ -27,6 +27,9 @@ export const createAdminUser = async (
app_metadata: { app_metadata: {
user_id: "admin_user", user_id: "admin_user",
}, },
provider_metadata: {
password: password_hash,
},
}) })
const token = jwt.sign(authUser, "test") const token = jwt.sign(authUser, "test")
+18 -13
View File
@@ -1,8 +1,4 @@
import { import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
AuthenticationInput,
AuthenticationResponse,
ModulesSdkTypes,
} from "@medusajs/types"
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils" import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
import { AuthUserService } from "@services" import { AuthUserService } from "@services"
import jwt, { JwtPayload } from "jsonwebtoken" import jwt, { JwtPayload } from "jsonwebtoken"
@@ -12,13 +8,13 @@ import url from "url"
type InjectedDependencies = { type InjectedDependencies = {
authUserService: AuthUserService authUserService: AuthUserService
authProviderService: ModulesSdkTypes.InternalModuleService<any>
} }
type ProviderConfig = { type ProviderConfig = {
clientID: string clientID: string
clientSecret: string clientSecret: string
callbackURL: string callbackURL: string
successRedirectUrl?: string
} }
class GoogleProvider extends AbstractAuthModuleProvider { class GoogleProvider extends AbstractAuthModuleProvider {
@@ -26,16 +22,14 @@ class GoogleProvider extends AbstractAuthModuleProvider {
public static DISPLAY_NAME = "Google Authentication" public static DISPLAY_NAME = "Google Authentication"
protected readonly authUserService_: AuthUserService protected readonly authUserService_: AuthUserService
protected readonly authProviderService_: ModulesSdkTypes.InternalModuleService<any>
constructor({ authUserService, authProviderService }: InjectedDependencies) { constructor({ authUserService }: InjectedDependencies) {
super(arguments[0], { super(arguments[0], {
provider: GoogleProvider.PROVIDER, provider: GoogleProvider.PROVIDER,
displayName: GoogleProvider.DISPLAY_NAME, displayName: GoogleProvider.DISPLAY_NAME,
}) })
this.authUserService_ = authUserService this.authUserService_ = authUserService
this.authProviderService_ = authProviderService
} }
async authenticate( async authenticate(
@@ -112,7 +106,10 @@ class GoogleProvider extends AbstractAuthModuleProvider {
} }
} }
return { success: true, authUser } return {
success: true,
authUser,
}
} }
// abstractable // abstractable
@@ -130,7 +127,17 @@ class GoogleProvider extends AbstractAuthModuleProvider {
try { try {
const accessToken = await client.getToken(tokenParams) const accessToken = await client.getToken(tokenParams)
return await this.verify_(accessToken.token.id_token) const { authUser, success } = await this.verify_(
accessToken.token.id_token
)
const { successRedirectUrl } = this.getConfigFromScope()
return {
success,
authUser,
successRedirectUrl,
}
} catch (error) { } catch (error) {
return { success: false, error: error.message } return { success: false, error: error.message }
} }
@@ -165,8 +172,6 @@ class GoogleProvider extends AbstractAuthModuleProvider {
private async getProviderConfig( private async getProviderConfig(
req: AuthenticationInput req: AuthenticationInput
): Promise<ProviderConfig> { ): Promise<ProviderConfig> {
await this.authProviderService_.retrieve(GoogleProvider.PROVIDER)
const config = this.getConfigFromScope() const config = this.getConfigFromScope()
const callbackURL = config.callbackURL const callbackURL = config.callbackURL
+5 -2
View File
@@ -14,6 +14,7 @@ import {
import { AuthUser } from "@models" import { AuthUser } from "@models"
type InjectedDependencies = { type InjectedDependencies = {
baseRepository: DAL.RepositoryService
authUserRepository: DAL.RepositoryService authUserRepository: DAL.RepositoryService
} }
@@ -23,11 +24,13 @@ export default class AuthUserService<
AuthUser AuthUser
)<TEntity> { )<TEntity> {
protected readonly authUserRepository_: RepositoryService<TEntity> protected readonly authUserRepository_: RepositoryService<TEntity>
protected baseRepository_: DAL.RepositoryService
constructor(container: InjectedDependencies) { constructor(container: InjectedDependencies) {
// @ts-ignore // @ts-ignore
super(...arguments) super(...arguments)
this.authUserRepository_ = container.authUserRepository this.authUserRepository_ = container.authUserRepository
this.baseRepository_ = container.baseRepository
} }
@InjectManager("authUserRepository_") @InjectManager("authUserRepository_")
@@ -36,7 +39,7 @@ export default class AuthUserService<
provider: string, provider: string,
config: FindConfig<TEntityMethod> = {}, config: FindConfig<TEntityMethod> = {},
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<TEntity> { ): Promise<AuthTypes.AuthUserDTO> {
const queryConfig = ModulesSdkUtils.buildQuery<TEntity>( const queryConfig = ModulesSdkUtils.buildQuery<TEntity>(
{ entity_id: entityId, provider }, { entity_id: entityId, provider },
{ ...config, take: 1 } { ...config, take: 1 }
@@ -53,6 +56,6 @@ export default class AuthUserService<
) )
} }
return result return await this.baseRepository_.serialize<AuthTypes.AuthUserDTO>(result)
} }
} }
@@ -1,11 +1,11 @@
import jwt from "jsonwebtoken"
import { MedusaError } from "@medusajs/utils"
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { AuthenticationInput, IAuthModuleService } from "@medusajs/types" import { AuthenticationInput, IAuthModuleService } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import jwt from "jsonwebtoken"
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { scope, authProvider } = req.params const { scope, auth_provider } = req.params
const service: IAuthModuleService = req.scope.resolve( const service: IAuthModuleService = req.scope.resolve(
ModuleRegistrationName.AUTH ModuleRegistrationName.AUTH
@@ -20,18 +20,23 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
protocol: req.protocol, protocol: req.protocol,
} as AuthenticationInput } as AuthenticationInput
const authResult = await service.validateCallback(authProvider, authData) const authResult = await service.validateCallback(auth_provider, authData)
const { success, error, authUser, location } = authResult const { success, error, authUser, successRedirectUrl } = authResult
if (location) {
res.redirect(location)
return
}
if (success) { if (success) {
const { jwt_secret } = req.scope.resolve("configModule").projectConfig const { jwt_secret } = req.scope.resolve("configModule").projectConfig
const token = jwt.sign(authUser, jwt_secret) const token = jwt.sign(authUser, jwt_secret)
return res.status(200).json({ token })
if (successRedirectUrl) {
const url = new URL(successRedirectUrl!)
url.searchParams.append("auth_token", token)
return res.redirect(url.toString())
}
return res.json({ token })
} }
throw new MedusaError( throw new MedusaError(
@@ -5,7 +5,7 @@ import jwt from "jsonwebtoken"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { scope, authProvider } = req.params const { scope, auth_provider } = req.params
const service: IAuthModuleService = req.scope.resolve( const service: IAuthModuleService = req.scope.resolve(
ModuleRegistrationName.AUTH ModuleRegistrationName.AUTH
@@ -20,7 +20,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
protocol: req.protocol, protocol: req.protocol,
} as AuthenticationInput } as AuthenticationInput
const authResult = await service.authenticate(authProvider, authData) const authResult = await service.authenticate(auth_provider, authData)
const { success, error, authUser, location } = authResult const { success, error, authUser, location } = authResult
@@ -7,4 +7,14 @@ export const authRoutesMiddlewares: MiddlewareRoute[] = [
matcher: "/auth/session", matcher: "/auth/session",
middlewares: [authenticate(/.*/, "bearer")], middlewares: [authenticate(/.*/, "bearer")],
}, },
{
method: ["POST"],
matcher: "/auth/:scope/:auth_provider/callback",
middlewares: [],
},
{
method: ["POST"],
matcher: "/auth/:scope/:auth_provider",
middlewares: [],
},
] ]
@@ -24,6 +24,11 @@ export type AuthenticationResponse = {
* Redirect location. Location takes precedence over success. * Redirect location. Location takes precedence over success.
*/ */
location?: string location?: string
/**
* Redirect url for successful authentication.
*/
successRedirectUrl?: string
} }
/** /**