feat: Reset password (#8962)
* wip * more work * wip * more work * wrap up first iteration * work on new approach * more work * move middleware func to route * cleanup * more work * wrap up * more work * fix workflow * minor tweaks * finalize * Use JWT secret instead
This commit is contained in:
1 parent
2c5e72d141
commit
e27056b3c3
29 files changed
+539
-100
No files matched your search
@@ -1,3 +1,4 @@
|
|||||||
|
import { generateResetPasswordTokenWorkflow } from "@medusajs/core-flows"
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
import {
|
import {
|
||||||
adminHeaders,
|
adminHeaders,
|
||||||
@@ -8,10 +9,17 @@ jest.setTimeout(30000)
|
|||||||
|
|
||||||
medusaIntegrationTestRunner({
|
medusaIntegrationTestRunner({
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
|
let container
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
container = getContainer()
|
||||||
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Full authentication lifecycle", () => {
|
||||||
it("Invite + registration + authentication flow", async () => {
|
it("Invite + registration + authentication flow", async () => {
|
||||||
// Create invite
|
// Create invite
|
||||||
const { token: inviteToken } = (
|
const { token: inviteToken } = (
|
||||||
@@ -121,7 +129,130 @@ medusaIntegrationTestRunner({
|
|||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(signup.response.status).toEqual(401)
|
expect(signup.response.status).toEqual(401)
|
||||||
expect(signup.response.data.message).toEqual("Invalid email or password")
|
expect(signup.response.data.message).toEqual(
|
||||||
|
"Invalid email or password"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Reset password flows", () => {
|
||||||
|
it("should generate a reset password token", async () => {
|
||||||
|
const response = await api.post("/auth/user/emailpass/reset-password", {
|
||||||
|
email: "admin@medusa.js",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(201)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to generate token for non-existing user, but still respond with 201", async () => {
|
||||||
|
const response = await api.post("/auth/user/emailpass/reset-password", {
|
||||||
|
email: "non-existing-user@medusa.js",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(201)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to generate token for existing user but no provider, but still respond with 201", async () => {
|
||||||
|
const response = await api.post(
|
||||||
|
"/auth/user/non-existing-provider/reset-password",
|
||||||
|
{ email: "admin@medusa.js" }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(201)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to generate token for existing user but no provider, but still respond with 201", async () => {
|
||||||
|
const response = await api.post(
|
||||||
|
"/auth/user/non-existing-provider/reset-password",
|
||||||
|
{ email: "admin@medusa.js" }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(201)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should successfully reset password", async () => {
|
||||||
|
// Register user
|
||||||
|
await api.post("/auth/user/emailpass/register", {
|
||||||
|
email: "test@medusa-commerce.com",
|
||||||
|
password: "secret_password",
|
||||||
|
})
|
||||||
|
|
||||||
|
// The token won't be part of the Rest API response, so we need to generate it manually
|
||||||
|
const { result } = await generateResetPasswordTokenWorkflow(
|
||||||
|
container
|
||||||
|
).run({
|
||||||
|
input: {
|
||||||
|
entityId: "test@medusa-commerce.com",
|
||||||
|
provider: "emailpass",
|
||||||
|
secret: "test",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/auth/user/emailpass/update?token=${result}`,
|
||||||
|
{
|
||||||
|
email: "test@medusa-commerce.com",
|
||||||
|
password: "new_password",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data).toEqual({ success: true })
|
||||||
|
|
||||||
|
const failedLogin = await api
|
||||||
|
.post("/auth/user/emailpass", {
|
||||||
|
email: "test@medusa-commerce.com",
|
||||||
|
password: "secret_password",
|
||||||
|
})
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(failedLogin.response.status).toEqual(401)
|
||||||
|
expect(failedLogin.response.data.message).toEqual(
|
||||||
|
"Invalid email or password"
|
||||||
|
)
|
||||||
|
|
||||||
|
const login = await api.post("/auth/user/emailpass", {
|
||||||
|
email: "test@medusa-commerce.com",
|
||||||
|
password: "new_password",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(login.status).toEqual(200)
|
||||||
|
expect(login.data).toEqual({ token: expect.any(String) })
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail if token has expired", async () => {
|
||||||
|
jest.useFakeTimers()
|
||||||
|
|
||||||
|
// Register user
|
||||||
|
await api.post("/auth/user/emailpass/register", {
|
||||||
|
email: "test@medusa-commerce.com",
|
||||||
|
password: "secret_password",
|
||||||
|
})
|
||||||
|
|
||||||
|
// The token won't be part of the Rest API response, so we need to generate it manually
|
||||||
|
const { result } = await generateResetPasswordTokenWorkflow(
|
||||||
|
container
|
||||||
|
).run({
|
||||||
|
input: {
|
||||||
|
entityId: "test@medusa-commerce.com",
|
||||||
|
provider: "emailpass",
|
||||||
|
secret: "test",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Advance time by 15 minutes
|
||||||
|
jest.advanceTimersByTime(15 * 60 * 1000)
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post(`/auth/user/emailpass/update?token=${result}`, {
|
||||||
|
email: "test@medusa-commerce.com",
|
||||||
|
password: "new_password",
|
||||||
|
})
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.response.status).toEqual(401)
|
||||||
|
expect(response.response.data.message).toEqual("Invalid token")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1 +1,3 @@
|
|||||||
export * from "./steps"
|
export * from "./steps"
|
||||||
|
export * from "./workflows"
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export const setAuthAppMetadataStep = createStep(
|
|||||||
|
|
||||||
appMetadata[key] = data.value
|
appMetadata[key] = data.value
|
||||||
|
|
||||||
await service.updateAuthIdentites({
|
await service.updateAuthIdentities({
|
||||||
id: authIdentity.id,
|
id: authIdentity.id,
|
||||||
app_metadata: appMetadata,
|
app_metadata: appMetadata,
|
||||||
})
|
})
|
||||||
@@ -58,7 +58,7 @@ export const setAuthAppMetadataStep = createStep(
|
|||||||
delete appMetadata[key]
|
delete appMetadata[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
await service.updateAuthIdentites({
|
await service.updateAuthIdentities({
|
||||||
id: authIdentity.id,
|
id: authIdentity.id,
|
||||||
app_metadata: appMetadata,
|
app_metadata: appMetadata,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
AuthWorkflowEvents,
|
||||||
|
generateJwtToken,
|
||||||
|
MedusaError,
|
||||||
|
} from "@medusajs/utils";
|
||||||
|
import {
|
||||||
|
createWorkflow,
|
||||||
|
transform,
|
||||||
|
WorkflowResponse,
|
||||||
|
} from "@medusajs/workflows-sdk";
|
||||||
|
import { emitEventStep, useRemoteQueryStep } from "../../common";
|
||||||
|
|
||||||
|
export const generateResetPasswordTokenWorkflow = createWorkflow(
|
||||||
|
"generate-reset-password-token",
|
||||||
|
(input: { entityId: string; provider: string; secret: string }) => {
|
||||||
|
const providerIdentities = useRemoteQueryStep({
|
||||||
|
entry_point: "provider_identity",
|
||||||
|
fields: ["auth_identity_id", "provider_metadata"],
|
||||||
|
variables: {
|
||||||
|
filters: {
|
||||||
|
entity_id: input.entityId,
|
||||||
|
provider: input.provider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const token = transform(
|
||||||
|
{ input, providerIdentities },
|
||||||
|
({ input, providerIdentities }) => {
|
||||||
|
const providerIdentity = providerIdentities?.[0]
|
||||||
|
|
||||||
|
if (!providerIdentity) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Provider identity with entity_id ${input.entityId} and provider ${input.provider} not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = generateJwtToken(
|
||||||
|
{
|
||||||
|
entity_id: input.entityId,
|
||||||
|
provider: input.provider,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secret: input.secret,
|
||||||
|
expiresIn: "15m",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
emitEventStep({
|
||||||
|
eventName: AuthWorkflowEvents.PASSWORD_RESET,
|
||||||
|
data: { entity_id: input.entityId, token },
|
||||||
|
})
|
||||||
|
|
||||||
|
return new WorkflowResponse(token)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./generate-reset-password-token"
|
||||||
@@ -83,6 +83,11 @@ export type ProviderIdentityDTO = {
|
|||||||
*/
|
*/
|
||||||
entity_id: string
|
entity_id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the auth identity linked to the provider identity.
|
||||||
|
*/
|
||||||
|
auth_identity_id?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The auth identity linked to the provider identity.
|
* The auth identity linked to the provider identity.
|
||||||
*/
|
*/
|
||||||
@@ -205,6 +210,11 @@ export interface FilterableProviderIdentityProps
|
|||||||
*/
|
*/
|
||||||
entity_id?: string
|
entity_id?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the provider identities by the ID of the auth identity they are linked to.
|
||||||
|
*/
|
||||||
|
auth_identity_id?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The provider handle to filter the provider identity by.
|
* The provider handle to filter the provider identity by.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -41,4 +41,8 @@ export interface IAuthProvider {
|
|||||||
data: AuthenticationInput,
|
data: AuthenticationInput,
|
||||||
authIdentityProviderService: AuthIdentityProviderService
|
authIdentityProviderService: AuthIdentityProviderService
|
||||||
): Promise<AuthenticationResponse>
|
): Promise<AuthenticationResponse>
|
||||||
|
update: (
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
authIdentityProviderService: AuthIdentityProviderService
|
||||||
|
) => Promise<AuthenticationResponse>
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,11 @@ export interface IAuthModuleService extends IModuleService {
|
|||||||
providerData: AuthenticationInput
|
providerData: AuthenticationInput
|
||||||
): Promise<AuthenticationResponse>
|
): Promise<AuthenticationResponse>
|
||||||
|
|
||||||
|
updateProvider(
|
||||||
|
provider: string,
|
||||||
|
providerData: Record<string, unknown>
|
||||||
|
): Promise<AuthenticationResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When authenticating users with a third-party provider, such as Google, the user performs an
|
* When authenticating users with a third-party provider, such as Google, the user performs an
|
||||||
* action to finish the authentication, such as enter their credentials in Google's sign-in
|
* action to finish the authentication, such as enter their credentials in Google's sign-in
|
||||||
@@ -244,13 +249,13 @@ export interface IAuthModuleService extends IModuleService {
|
|||||||
* @returns {Promise<AuthIdentityDTO[]>} The updated auths.
|
* @returns {Promise<AuthIdentityDTO[]>} The updated auths.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const authIdentities = await authModuleService.updateAuthIdentites([
|
* const authIdentities = await authModuleService.updateAuthIdentities([
|
||||||
* {
|
* {
|
||||||
* id: "authusr_123",
|
* id: "authusr_123",
|
||||||
* },
|
* },
|
||||||
* ])
|
* ])
|
||||||
*/
|
*/
|
||||||
updateAuthIdentites(
|
updateAuthIdentities(
|
||||||
data: UpdateAuthIdentityDTO[],
|
data: UpdateAuthIdentityDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<AuthIdentityDTO[]>
|
): Promise<AuthIdentityDTO[]>
|
||||||
@@ -263,11 +268,11 @@ export interface IAuthModuleService extends IModuleService {
|
|||||||
* @returns {Promise<AuthIdentityDTO>} The updated auth.
|
* @returns {Promise<AuthIdentityDTO>} The updated auth.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const authIdentity = await authModuleService.updateAuthIdentites({
|
* const authIdentity = await authModuleService.updateAuthIdentities({
|
||||||
* id: "authusr_123",
|
* id: "authusr_123",
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
updateAuthIdentites(
|
updateAuthIdentities(
|
||||||
data: UpdateAuthIdentityDTO,
|
data: UpdateAuthIdentityDTO,
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<AuthIdentityDTO>
|
): Promise<AuthIdentityDTO>
|
||||||
@@ -399,7 +404,7 @@ export interface IAuthModuleService extends IModuleService {
|
|||||||
* },
|
* },
|
||||||
* ])
|
* ])
|
||||||
*/
|
*/
|
||||||
updateProviderIdentites(
|
updateProviderIdentities(
|
||||||
data: UpdateProviderIdentityDTO[],
|
data: UpdateProviderIdentityDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<ProviderIdentityDTO[]>
|
): Promise<ProviderIdentityDTO[]>
|
||||||
@@ -412,11 +417,11 @@ export interface IAuthModuleService extends IModuleService {
|
|||||||
* @returns {Promise<ProviderIdentityDTO>} The updated provider identity.
|
* @returns {Promise<ProviderIdentityDTO>} The updated provider identity.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const providerIdentity = await authModuleService.updateProviderIdentites({
|
* const providerIdentity = await authModuleService.updateProviderIdentities({
|
||||||
* id: "provider_123",
|
* id: "provider_123",
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
updateProviderIdentites(
|
updateProviderIdentities(
|
||||||
data: UpdateProviderIdentityDTO,
|
data: UpdateProviderIdentityDTO,
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<ProviderIdentityDTO>
|
): Promise<ProviderIdentityDTO>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
AuthenticationInput,
|
AuthenticationInput,
|
||||||
AuthenticationResponse,
|
AuthenticationResponse,
|
||||||
AuthIdentityProviderService,
|
AuthIdentityProviderService,
|
||||||
IAuthProvider,
|
IAuthProvider
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,6 +238,15 @@ export abstract class AbstractAuthModuleProvider implements IAuthProvider {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
authIdentityProviderService: AuthIdentityProviderService
|
||||||
|
): Promise<AuthenticationResponse> {
|
||||||
|
throw new Error(
|
||||||
|
`Method 'update' not implemented for provider ${this.provider}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method validates the callback of an authentication request.
|
* This method validates the callback of an authentication request.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ export const OrderWorkflowEvents = {
|
|||||||
COMPLETED: "order.completed",
|
COMPLETED: "order.completed",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UserWorkflowEvents = {
|
||||||
|
CREATED: "user.created",
|
||||||
|
UPDATED: "user.updated",
|
||||||
|
DELETED: "user.deleted",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AuthWorkflowEvents = {
|
||||||
|
PASSWORD_RESET: "auth.password_reset",
|
||||||
|
}
|
||||||
|
|
||||||
export const SalesChannelWorkflowEvents = {
|
export const SalesChannelWorkflowEvents = {
|
||||||
CREATED: "sales-channel.created",
|
CREATED: "sales-channel.created",
|
||||||
UPDATED: "sales-channel.updated",
|
UPDATED: "sales-channel.updated",
|
||||||
|
|||||||
@@ -7,9 +7,3 @@ export const UserEvents = {
|
|||||||
...buildEventNamesFromEntityName(eventBaseNames, Modules.USER),
|
...buildEventNamesFromEntityName(eventBaseNames, Modules.USER),
|
||||||
INVITE_TOKEN_GENERATED: `${Modules.USER}.user.invite.token_generated`,
|
INVITE_TOKEN_GENERATED: `${Modules.USER}.user.invite.token_generated`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserWorkflowEvents = {
|
|
||||||
CREATED: "user.created",
|
|
||||||
UPDATED: "user.updated",
|
|
||||||
DELETED: "user.deleted",
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
|
import { MiddlewareRoute } from "@medusajs/framework"
|
||||||
|
import { authenticate } from "../../../utils/middlewares/authenticate-middleware"
|
||||||
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AdminGetUserParams,
|
AdminGetUserParams,
|
||||||
AdminGetUsersParams,
|
AdminGetUsersParams,
|
||||||
AdminUpdateUser,
|
AdminUpdateUser
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
import { MiddlewareRoute } from "@medusajs/framework"
|
|
||||||
import { authenticate } from "../../../utils/middlewares/authenticate-middleware"
|
|
||||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
|
||||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
|
||||||
|
|
||||||
// TODO: Due to issues with our routing (and using router.use for applying middlewares), we have to opt-out of global auth in all routes, and then reapply it here.
|
// TODO: Due to issues with our routing (and using router.use for applying middlewares), we have to opt-out of global auth in all routes, and then reapply it here.
|
||||||
// See https://medusacorp.slack.com/archives/C025KMS13SA/p1716455350491879 for details.
|
// See https://medusacorp.slack.com/archives/C025KMS13SA/p1716455350491879 for details.
|
||||||
export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
|
|||||||
@@ -38,3 +38,4 @@ export const AdminUpdateUser = z.object({
|
|||||||
last_name: z.string().nullish(),
|
last_name: z.string().nullish(),
|
||||||
avatar_url: z.string().nullish(),
|
avatar_url: z.string().nullish(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { AuthenticationInput, IAuthModuleService } from "@medusajs/types"
|
||||||
AuthenticationInput,
|
|
||||||
ConfigModule,
|
|
||||||
IAuthModuleService,
|
|
||||||
} from "@medusajs/types"
|
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
@@ -13,21 +9,6 @@ import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
|||||||
|
|
||||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
const { actor_type, auth_provider } = req.params
|
const { actor_type, auth_provider } = req.params
|
||||||
const config: ConfigModule = req.scope.resolve(
|
|
||||||
ContainerRegistrationKeys.CONFIG_MODULE
|
|
||||||
)
|
|
||||||
|
|
||||||
const authMethodsPerActor =
|
|
||||||
config.projectConfig?.http?.authMethodsPerActor ?? {}
|
|
||||||
// Not having the config defined would allow for all auth providers for the particular actor.
|
|
||||||
if (authMethodsPerActor[actor_type]) {
|
|
||||||
if (!authMethodsPerActor[actor_type].includes(auth_provider)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`The actor type ${actor_type} is not allowed to use the auth provider ${auth_provider}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const service: IAuthModuleService = req.scope.resolve(
|
const service: IAuthModuleService = req.scope.resolve(
|
||||||
ModuleRegistrationName.AUTH
|
ModuleRegistrationName.AUTH
|
||||||
|
|||||||
@@ -17,18 +17,6 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
ContainerRegistrationKeys.CONFIG_MODULE
|
ContainerRegistrationKeys.CONFIG_MODULE
|
||||||
)
|
)
|
||||||
|
|
||||||
const authMethodsPerActor =
|
|
||||||
config.projectConfig?.http?.authMethodsPerActor ?? {}
|
|
||||||
// Not having the config defined would allow for all auth providers for the particular actor.
|
|
||||||
if (authMethodsPerActor[actor_type]) {
|
|
||||||
if (!authMethodsPerActor[actor_type].includes(auth_provider)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`The actor type ${actor_type} is not allowed to use the auth provider ${auth_provider}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const service: IAuthModuleService = req.scope.resolve(
|
const service: IAuthModuleService = req.scope.resolve(
|
||||||
ModuleRegistrationName.AUTH
|
ModuleRegistrationName.AUTH
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { generateResetPasswordTokenWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../../../types/routing"
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const { auth_provider } = req.params
|
||||||
|
const { identifier } = req.body
|
||||||
|
|
||||||
|
const { http } = req.scope.resolve(
|
||||||
|
ContainerRegistrationKeys.CONFIG_MODULE
|
||||||
|
).projectConfig
|
||||||
|
|
||||||
|
await generateResetPasswordTokenWorkflow(req.scope).run({
|
||||||
|
input: {
|
||||||
|
entityId: identifier,
|
||||||
|
provider: auth_provider,
|
||||||
|
secret: http.jwtSecret as string,
|
||||||
|
},
|
||||||
|
throwOnError: false, // we don't want to throw on error to avoid leaking information about non-existing identities
|
||||||
|
})
|
||||||
|
|
||||||
|
res.sendStatus(201)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AUTHENTICATE = false
|
||||||
@@ -17,18 +17,6 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
ContainerRegistrationKeys.CONFIG_MODULE
|
ContainerRegistrationKeys.CONFIG_MODULE
|
||||||
)
|
)
|
||||||
|
|
||||||
const authMethodsPerActor =
|
|
||||||
config.projectConfig?.http?.authMethodsPerActor ?? {}
|
|
||||||
// Not having the config defined would allow for all auth providers for the particular actor.
|
|
||||||
if (authMethodsPerActor[actor_type]) {
|
|
||||||
if (!authMethodsPerActor[actor_type].includes(auth_provider)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`The actor type ${actor_type} is not allowed to use the auth provider ${auth_provider}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const service: IAuthModuleService = req.scope.resolve(
|
const service: IAuthModuleService = req.scope.resolve(
|
||||||
ModuleRegistrationName.AUTH
|
ModuleRegistrationName.AUTH
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { AuthenticatedMedusaRequest } from "@medusajs/framework"
|
||||||
|
import { IAuthModuleService } from "@medusajs/types"
|
||||||
|
import { MedusaError, ModuleRegistrationName } from "@medusajs/utils"
|
||||||
|
import { MedusaResponse } from "../../../../../types/routing"
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const { auth_provider } = req.params
|
||||||
|
|
||||||
|
const authService = req.scope.resolve<IAuthModuleService>(
|
||||||
|
ModuleRegistrationName.AUTH
|
||||||
|
)
|
||||||
|
|
||||||
|
const { authIdentity, success, error } = await authService.updateProvider(
|
||||||
|
auth_provider,
|
||||||
|
req.body as Record<string, unknown>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (success && authIdentity) {
|
||||||
|
return res.status(200).json({ success: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MedusaError(MedusaError.Types.UNAUTHORIZED, error || "Unauthorized")
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { MiddlewareRoute } from "@medusajs/framework"
|
import { MiddlewareRoute } from "@medusajs/framework"
|
||||||
import { authenticate } from "../../utils/middlewares/authenticate-middleware"
|
import { authenticate } from "../../utils/middlewares/authenticate-middleware"
|
||||||
|
import { validateScopeProviderAssociation } from "./utils/validate-scope-provider-association"
|
||||||
|
import { validateToken } from "./utils/validate-token"
|
||||||
|
|
||||||
export const authRoutesMiddlewares: MiddlewareRoute[] = [
|
export const authRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
{
|
{
|
||||||
@@ -15,21 +17,31 @@ export const authRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/auth/:actor_type/:auth_provider/callback",
|
matcher: "/auth/:actor_type/:auth_provider/callback",
|
||||||
middlewares: [],
|
middlewares: [validateScopeProviderAssociation()],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/auth/:actor_type/:auth_provider/register",
|
matcher: "/auth/:actor_type/:auth_provider/register",
|
||||||
middlewares: [],
|
middlewares: [validateScopeProviderAssociation()],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/auth/:actor_type/:auth_provider",
|
matcher: "/auth/:actor_type/:auth_provider",
|
||||||
middlewares: [],
|
middlewares: [validateScopeProviderAssociation()],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/auth/:actor_type/:auth_provider",
|
matcher: "/auth/:actor_type/:auth_provider",
|
||||||
middlewares: [],
|
middlewares: [validateScopeProviderAssociation()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/auth/:actor_type/:auth_provider/reset-password",
|
||||||
|
middlewares: [validateScopeProviderAssociation()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/auth/:actor_type/:auth_provider/update",
|
||||||
|
middlewares: [validateScopeProviderAssociation(), validateToken()],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
MedusaNextFunction,
|
||||||
|
MedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "@medusajs/framework"
|
||||||
|
import { ConfigModule } from "@medusajs/types"
|
||||||
|
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
|
||||||
|
|
||||||
|
// Middleware to validate that a scope is associated with a provider
|
||||||
|
export const validateScopeProviderAssociation = () => {
|
||||||
|
return async (
|
||||||
|
req: MedusaRequest,
|
||||||
|
_: MedusaResponse,
|
||||||
|
next: MedusaNextFunction
|
||||||
|
) => {
|
||||||
|
const { actor_type, auth_provider } = req.params
|
||||||
|
const config: ConfigModule = req.scope.resolve(
|
||||||
|
ContainerRegistrationKeys.CONFIG_MODULE
|
||||||
|
)
|
||||||
|
|
||||||
|
const authMethodsPerActor =
|
||||||
|
config.projectConfig?.http?.authMethodsPerActor ?? {}
|
||||||
|
// Not having the config defined would allow for all auth providers for the particular actor.
|
||||||
|
if (authMethodsPerActor[actor_type]) {
|
||||||
|
if (!authMethodsPerActor[actor_type].includes(auth_provider)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`The actor type ${actor_type} is not allowed to use the auth provider ${auth_provider}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaNextFunction,
|
||||||
|
MedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "@medusajs/framework"
|
||||||
|
import { ConfigModule, IAuthModuleService } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ContainerRegistrationKeys,
|
||||||
|
ModuleRegistrationName,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { decode, JwtPayload, verify } from "jsonwebtoken"
|
||||||
|
|
||||||
|
// Middleware to validate that a token is valid
|
||||||
|
export const validateToken = () => {
|
||||||
|
return async (
|
||||||
|
req: MedusaRequest,
|
||||||
|
res: MedusaResponse,
|
||||||
|
next: MedusaNextFunction
|
||||||
|
) => {
|
||||||
|
const { actor_type, auth_provider } = req.params
|
||||||
|
const { token } = req.query
|
||||||
|
|
||||||
|
const req_ = req as AuthenticatedMedusaRequest
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const { http } = req_.scope.resolve<ConfigModule>(
|
||||||
|
ContainerRegistrationKeys.CONFIG_MODULE
|
||||||
|
).projectConfig
|
||||||
|
|
||||||
|
const authModule = req.scope.resolve<IAuthModuleService>(
|
||||||
|
ModuleRegistrationName.AUTH
|
||||||
|
)
|
||||||
|
|
||||||
|
let decoded = decode(token as string) as JwtPayload
|
||||||
|
|
||||||
|
const [providerIdentity] = await authModule.listProviderIdentities(
|
||||||
|
{
|
||||||
|
entity_id: decoded.entity_id,
|
||||||
|
provider: auth_provider,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: ["provider_metadata", "auth_identity_id", "entity_id"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!providerIdentity) {
|
||||||
|
return res.status(401).json({ message: "Invalid token" })
|
||||||
|
}
|
||||||
|
|
||||||
|
let verified: JwtPayload | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
verified = verify(token as string, http.jwtSecret as string) as JwtPayload
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(401).json({ message: "Invalid token" })
|
||||||
|
}
|
||||||
|
|
||||||
|
req_.auth_context = {
|
||||||
|
actor_type,
|
||||||
|
auth_identity_id: verified.auth_identity_id!,
|
||||||
|
actor_id: providerIdentity.entity_id,
|
||||||
|
app_metadata: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { MiddlewareFunction } from "@medusajs/framework"
|
||||||
import { NextFunction } from "express"
|
import { NextFunction } from "express"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../types/routing"
|
||||||
import { MiddlewareFunction } from "@medusajs/framework"
|
|
||||||
|
|
||||||
// Due to how our route loader works, where we load all middlewares before routes, ambiguous routes end up having all middlewares on different routes executed before the route handler is.
|
// Due to how our route loader works, where we load all middlewares before routes, ambiguous routes end up having all middlewares on different routes executed before the route handler is.
|
||||||
// This function allows us to skip middlewares for particular routes, so we can temporarily solve this without completely breaking the route loader for everyone.
|
// This function allows us to skip middlewares for particular routes, so we can temporarily solve this without completely breaking the route loader for everyone.
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default async function ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We know the authIdentity is not undefined
|
// We know the authIdentity is not undefined
|
||||||
await authService.updateAuthIdentites({
|
await authService.updateAuthIdentities({
|
||||||
id: authIdentity!.id,
|
id: authIdentity!.id,
|
||||||
app_metadata: {
|
app_metadata: {
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
|
|||||||
+6
-6
@@ -215,7 +215,7 @@ moduleIntegrationTestRunner<IAuthModuleService>({
|
|||||||
let error
|
let error
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await service.updateAuthIdentites([
|
await service.updateAuthIdentities([
|
||||||
{
|
{
|
||||||
id: "does-not-exist",
|
id: "does-not-exist",
|
||||||
},
|
},
|
||||||
@@ -230,7 +230,7 @@ moduleIntegrationTestRunner<IAuthModuleService>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should update authIdentity", async () => {
|
it("should update authIdentity", async () => {
|
||||||
await service.updateAuthIdentites([
|
await service.updateAuthIdentities([
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
app_metadata: { email: "test@email.com" },
|
app_metadata: { email: "test@email.com" },
|
||||||
@@ -364,7 +364,7 @@ moduleIntegrationTestRunner<IAuthModuleService>({
|
|||||||
let error
|
let error
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await service.updateProviderIdentites([
|
await service.updateProviderIdentities([
|
||||||
{
|
{
|
||||||
id: "does-not-exist",
|
id: "does-not-exist",
|
||||||
},
|
},
|
||||||
@@ -382,18 +382,18 @@ moduleIntegrationTestRunner<IAuthModuleService>({
|
|||||||
let [providerIdentity] = await service.listProviderIdentities({
|
let [providerIdentity] = await service.listProviderIdentities({
|
||||||
entity_id,
|
entity_id,
|
||||||
})
|
})
|
||||||
await service.updateProviderIdentites([
|
await service.updateProviderIdentities([
|
||||||
{
|
{
|
||||||
id: providerIdentity.id,
|
id: providerIdentity.id,
|
||||||
provider_metadata: { email: "test@email.com" },
|
provider_metadata: { email: "test@email.com" },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const providerIdentites = await service.listProviderIdentities({
|
const providerIdentities = await service.listProviderIdentities({
|
||||||
id: [providerIdentity.id],
|
id: [providerIdentity.id],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(providerIdentites[0]).toEqual(
|
expect(providerIdentities[0]).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider_metadata: expect.objectContaining({
|
provider_metadata: expect.objectContaining({
|
||||||
email: "test@email.com",
|
email: "test@email.com",
|
||||||
|
|||||||
+17
-3
@@ -1,8 +1,8 @@
|
|||||||
import { IAuthModuleService } from "@medusajs/types"
|
import { IAuthModuleService } from "@medusajs/types"
|
||||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
|
||||||
import { resolve } from "path"
|
|
||||||
import { Module, Modules } from "@medusajs/utils"
|
import { Module, Modules } from "@medusajs/utils"
|
||||||
import { AuthModuleService } from "@services"
|
import { AuthModuleService } from "@services"
|
||||||
|
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||||
|
import { resolve } from "path"
|
||||||
|
|
||||||
let moduleOptions = {
|
let moduleOptions = {
|
||||||
providers: [
|
providers: [
|
||||||
@@ -42,7 +42,10 @@ moduleIntegrationTestRunner({
|
|||||||
service: AuthModuleService,
|
service: AuthModuleService,
|
||||||
}).linkable
|
}).linkable
|
||||||
|
|
||||||
expect(Object.keys(linkable)).toEqual(["authIdentity"])
|
expect(Object.keys(linkable)).toEqual([
|
||||||
|
"authIdentity",
|
||||||
|
"providerIdentity",
|
||||||
|
])
|
||||||
|
|
||||||
linkable.authIdentity.toJSON = undefined
|
linkable.authIdentity.toJSON = undefined
|
||||||
|
|
||||||
@@ -54,6 +57,17 @@ moduleIntegrationTestRunner({
|
|||||||
field: "authIdentity",
|
field: "authIdentity",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
linkable.providerIdentity.toJSON = undefined
|
||||||
|
|
||||||
|
expect(linkable.providerIdentity).toEqual({
|
||||||
|
id: {
|
||||||
|
linkable: "provider_identity_id",
|
||||||
|
primaryKey: "id",
|
||||||
|
serviceName: "auth",
|
||||||
|
field: "providerIdentity",
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("it fails if the provider does not exist", async () => {
|
it("it fails if the provider does not exist", async () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AuthIdentity } from "@models"
|
|
||||||
import { defineJoinerConfig, Modules } from "@medusajs/utils"
|
import { defineJoinerConfig, Modules } from "@medusajs/utils"
|
||||||
|
import { AuthIdentity, ProviderIdentity } from "@models"
|
||||||
|
|
||||||
export const joinerConfig = defineJoinerConfig(Modules.AUTH, {
|
export const joinerConfig = defineJoinerConfig(Modules.AUTH, {
|
||||||
models: [AuthIdentity],
|
models: [AuthIdentity, ProviderIdentity],
|
||||||
})
|
})
|
||||||
@@ -9,17 +9,14 @@ import {
|
|||||||
ModuleJoinerConfig,
|
ModuleJoinerConfig,
|
||||||
ModulesSdkTypes,
|
ModulesSdkTypes,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
import { AuthIdentity, ProviderIdentity } from "@models"
|
|
||||||
|
|
||||||
import { joinerConfig } from "../joiner-config"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InjectManager,
|
InjectManager,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
MedusaService,
|
MedusaService,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
import { AuthIdentity, ProviderIdentity } from "@models"
|
||||||
|
import { joinerConfig } from "../joiner-config"
|
||||||
import AuthProviderService from "./auth-provider"
|
import AuthProviderService from "./auth-provider"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
@@ -92,18 +89,19 @@ export default class AuthModuleService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Update to follow convention
|
// TODO: Update to follow convention
|
||||||
updateAuthIdentites(
|
// @ts-expect-error
|
||||||
|
updateAuthIdentities(
|
||||||
data: AuthTypes.UpdateAuthIdentityDTO[],
|
data: AuthTypes.UpdateAuthIdentityDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<AuthTypes.AuthIdentityDTO[]>
|
): Promise<AuthTypes.AuthIdentityDTO[]>
|
||||||
|
|
||||||
updateAuthIdentites(
|
updateAuthIdentities(
|
||||||
data: AuthTypes.UpdateAuthIdentityDTO,
|
data: AuthTypes.UpdateAuthIdentityDTO,
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<AuthTypes.AuthIdentityDTO>
|
): Promise<AuthTypes.AuthIdentityDTO>
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async updateAuthIdentites(
|
async updateAuthIdentities(
|
||||||
data: AuthTypes.UpdateAuthIdentityDTO | AuthTypes.UpdateAuthIdentityDTO[],
|
data: AuthTypes.UpdateAuthIdentityDTO | AuthTypes.UpdateAuthIdentityDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<AuthTypes.AuthIdentityDTO | AuthTypes.AuthIdentityDTO[]> {
|
): Promise<AuthTypes.AuthIdentityDTO | AuthTypes.AuthIdentityDTO[]> {
|
||||||
@@ -135,6 +133,7 @@ export default class AuthModuleService
|
|||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
createProviderIdentities(
|
createProviderIdentities(
|
||||||
data: AuthTypes.CreateProviderIdentityDTO[],
|
data: AuthTypes.CreateProviderIdentityDTO[],
|
||||||
@@ -163,18 +162,19 @@ export default class AuthModuleService
|
|||||||
>(providerIdentities)
|
>(providerIdentities)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProviderIdentites(
|
// @ts-expect-error
|
||||||
|
updateProviderIdentities(
|
||||||
data: AuthTypes.UpdateProviderIdentityDTO[],
|
data: AuthTypes.UpdateProviderIdentityDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<AuthTypes.ProviderIdentityDTO[]>
|
): Promise<AuthTypes.ProviderIdentityDTO[]>
|
||||||
|
|
||||||
updateProviderIdentites(
|
updateProviderIdentities(
|
||||||
data: AuthTypes.UpdateProviderIdentityDTO,
|
data: AuthTypes.UpdateProviderIdentityDTO,
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<AuthTypes.ProviderIdentityDTO>
|
): Promise<AuthTypes.ProviderIdentityDTO>
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async updateProviderIdentites(
|
async updateProviderIdentities(
|
||||||
data:
|
data:
|
||||||
| AuthTypes.UpdateProviderIdentityDTO
|
| AuthTypes.UpdateProviderIdentityDTO
|
||||||
| AuthTypes.UpdateProviderIdentityDTO[],
|
| AuthTypes.UpdateProviderIdentityDTO[],
|
||||||
@@ -192,6 +192,21 @@ export default class AuthModuleService
|
|||||||
return Array.isArray(data) ? serializedProviders : serializedProviders[0]
|
return Array.isArray(data) ? serializedProviders : serializedProviders[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateProvider(
|
||||||
|
provider: string,
|
||||||
|
data: Record<string, unknown>
|
||||||
|
): Promise<AuthenticationResponse> {
|
||||||
|
try {
|
||||||
|
return await this.authProviderService_.update(
|
||||||
|
provider,
|
||||||
|
data,
|
||||||
|
this.getAuthIdentityProviderService(provider)
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async authenticate(
|
async authenticate(
|
||||||
provider: string,
|
provider: string,
|
||||||
authenticationData: AuthenticationInput
|
authenticationData: AuthenticationInput
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
AuthIdentityProviderService,
|
AuthIdentityProviderService,
|
||||||
AuthTypes,
|
AuthTypes,
|
||||||
AuthenticationInput,
|
AuthenticationInput,
|
||||||
AuthenticationResponse,
|
AuthenticationResponse
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { MedusaError } from "@medusajs/utils"
|
import { MedusaError } from "@medusajs/utils"
|
||||||
import { AuthProviderRegistrationPrefix } from "@types"
|
import { AuthProviderRegistrationPrefix } from "@types"
|
||||||
@@ -51,6 +51,15 @@ export default class AuthProviderService {
|
|||||||
return await providerHandler.register(auth, authIdentityProviderService)
|
return await providerHandler.register(auth, authIdentityProviderService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
provider: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
authIdentityProviderService: AuthIdentityProviderService
|
||||||
|
): Promise<AuthenticationResponse> {
|
||||||
|
const providerHandler = this.retrieveProviderRegistration(provider)
|
||||||
|
return await providerHandler.update(data, authIdentityProviderService)
|
||||||
|
}
|
||||||
|
|
||||||
async validateCallback(
|
async validateCallback(
|
||||||
provider: string,
|
provider: string,
|
||||||
auth: AuthenticationInput,
|
auth: AuthenticationInput,
|
||||||
|
|||||||
@@ -35,14 +35,56 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
this.logger_ = logger
|
this.logger_ = logger
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async createAuthIdentity({ email, password, authIdentityService }) {
|
protected async hashPassword(password: string) {
|
||||||
const hashConfig = this.config_.hashConfig ?? { logN: 15, r: 8, p: 1 }
|
const hashConfig = this.config_.hashConfig ?? { logN: 15, r: 8, p: 1 }
|
||||||
const passwordHash = await Scrypt.kdf(password, hashConfig)
|
const passwordHash = await Scrypt.kdf(password, hashConfig)
|
||||||
|
return passwordHash.toString("base64")
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
data: { email: string; password: string },
|
||||||
|
authIdentityService: AuthIdentityProviderService
|
||||||
|
) {
|
||||||
|
const { email, password } = data ?? {}
|
||||||
|
|
||||||
|
if (!email || !isString(email)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Cannot update ${this.provider} provider identity without email`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password || !isString(password)) {
|
||||||
|
return { success: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
let authIdentity
|
||||||
|
|
||||||
|
try {
|
||||||
|
const passwordHash = await this.hashPassword(password)
|
||||||
|
|
||||||
|
authIdentity = await authIdentityService.update(email, {
|
||||||
|
provider_metadata: {
|
||||||
|
password: passwordHash,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
authIdentity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async createAuthIdentity({ email, password, authIdentityService }) {
|
||||||
|
const passwordHash = await this.hashPassword(password)
|
||||||
|
|
||||||
const createdAuthIdentity = await authIdentityService.create({
|
const createdAuthIdentity = await authIdentityService.create({
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: passwordHash.toString("base64"),
|
password: passwordHash,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user