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:
Oli Juhl
2024-09-06 12:28:29 +02:00
committed by GitHub
parent 2c5e72d141
commit e27056b3c3
29 changed files with 633 additions and 194 deletions
@@ -1,8 +1,4 @@
import {
AuthenticationInput,
ConfigModule,
IAuthModuleService,
} from "@medusajs/types"
import { AuthenticationInput, IAuthModuleService } from "@medusajs/types"
import {
ContainerRegistrationKeys,
MedusaError,
@@ -13,21 +9,6 @@ import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
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(
ModuleRegistrationName.AUTH
@@ -17,18 +17,6 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
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(
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
)
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(
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")
}
+16 -4
View File
@@ -1,5 +1,7 @@
import { MiddlewareRoute } from "@medusajs/framework"
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[] = [
{
@@ -15,21 +17,31 @@ export const authRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
matcher: "/auth/:actor_type/:auth_provider/callback",
middlewares: [],
middlewares: [validateScopeProviderAssociation()],
},
{
method: ["POST"],
matcher: "/auth/:actor_type/:auth_provider/register",
middlewares: [],
middlewares: [validateScopeProviderAssociation()],
},
{
method: ["POST"],
matcher: "/auth/:actor_type/:auth_provider",
middlewares: [],
middlewares: [validateScopeProviderAssociation()],
},
{
method: ["GET"],
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()
}
}