feat: Add successRedirectUrl to auth options (#6792)
This commit is contained in:
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>,
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -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))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
@@ -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,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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+15
-10
@@ -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(
|
||||||
+2
-2
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user