Skip to content

Commit

Permalink
more work
Browse files Browse the repository at this point in the history
  • Loading branch information
olivermrbl committed Sep 5, 2024
1 parent 924a071 commit 5977d22
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 59 deletions.
6 changes: 2 additions & 4 deletions integration-tests/http/__tests__/auth/admin/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,8 @@ medusaIntegrationTestRunner({
password: "secret_password",
})

const token = await authModule.generateToken({
provider: "emailpass",
actor_type: "user",
entity_id: "test@medusa-commerce.com",
const token = await authModule.generateToken("test@medusa-commerce.com", "emailpass", {
secret: "test"
})

const response = await api.post(
Expand Down
18 changes: 16 additions & 2 deletions packages/core/core-flows/src/auth/steps/generate-token.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { ModuleRegistrationName } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"

export type GenerateTokenStepInput = {
entityId: string
provider: string
secret: string
expiry?: number
}

export const generateTokenStep = createStep(
"generate-token",
async (input: Record<string, unknown>, { container }) => {
async (input: GenerateTokenStepInput, { container }) => {
const authModule = container.resolve(ModuleRegistrationName.AUTH)

const token = await authModule.generateToken(input)
const token = await authModule.generateToken(
input.entityId,
input.provider,
{
secret: input.secret,
expiry: input.expiry,
}
)

return new StepResponse(token)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@ import { generateTokenStep } from "../steps/generate-token"

export const generateResetPasswordTokenWorkflow = createWorkflow(
"generate-reset-password-token",
(input: { payload: { entity_id: string } & Record<string, unknown> }) => {
const token = generateTokenStep(input.payload)
(input: {
entityId: string
provider: string
secret: string
expiry?: number
}) => {
const token = generateTokenStep({
entityId: input.entityId,
provider: input.provider,
secret: input.secret,
expiry: input.expiry,
})

emitEventStep({
eventName: AuthWorkflowEvents.PASSWORD_RESET,
data: { entity_id: input.payload.entity_id, token },
data: { entity_id: input.entityId, token },
})
}
)
9 changes: 8 additions & 1 deletion packages/core/types/src/auth/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ export interface IAuthModuleService extends IModuleService {
providerData: Record<string, unknown>
): Promise<AuthenticationResponse>

generateToken(payload: Record<string, unknown>): Promise<string>
generateToken(
entityId: string,
provider: string,
options: {
secret: string
expiry?: number
}
): Promise<string>

/**
* When authenticating users with a third-party provider, such as Google, the user performs an
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { generateResetPasswordTokenWorkflow } from "@medusajs/core-flows"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
Expand All @@ -8,18 +9,20 @@ export const POST = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const { auth_provider, actor_type } = req.params
const { auth_provider } = req.params

const { entity_id } = req.body

const payload = {
entity_id,
actor_type,
provider: auth_provider,
}
const { http } = req.scope.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
).projectConfig

await generateResetPasswordTokenWorkflow(req.scope).run({
input: { payload },
input: {
entityId: entity_id,
provider: auth_provider,
secret: http.jwtSecret!,
},
throwOnError: false, // we don't want to throw on error to avoid leaking information about non-existing identities
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,18 @@
import { AuthenticatedMedusaRequest } from "@medusajs/framework"
import { IAuthModuleService } from "@medusajs/types"
import { MedusaError, ModuleRegistrationName } from "@medusajs/utils"
import { JwtPayload, verify } from "jsonwebtoken"
import { MedusaResponse } from "../../../../../types/routing"

export const POST = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const { auth_provider } = req.params
const { token } = req.query

const authService = req.scope.resolve<IAuthModuleService>(
ModuleRegistrationName.AUTH
)

if (token && !req.auth_context) {
let verified: JwtPayload | null = null

try {
verified = verify(token as string, "secret") as JwtPayload
} catch (error) {
return res.status(401).json({ message: "Unauthorized" })
}

if (!verified) {
return res.status(401).json({ message: "Unauthorized" })
}

const providerIdentity = await authService.retrieveProviderIdentity(
verified?.provider_identity_id,
{
select: ["entity_id"],
relations: ["auth_identity"],
}
)

if (!providerIdentity) {
return res.status(401).json({ message: "Unauthorized" })
}
}

const { success, error } = await authService.updateProvider(
auth_provider,
req.body as Record<string, unknown>
Expand Down
4 changes: 3 additions & 1 deletion packages/medusa/src/api/auth/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,6 +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[] = [
{
Expand Down Expand Up @@ -42,8 +43,9 @@ export const authRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"],
matcher: "/auth/:actor_type/:auth_provider/update",
middlewares: [
authenticate("*", ["bearer", "session"], { allowUnauthenticated: true }),
validateScopeProviderAssociation(),
validateToken(),
authenticate("*", ["bearer", "session"], { allowUnauthenticated: true }),
],
},
]
71 changes: 71 additions & 0 deletions packages/medusa/src/api/auth/utils/validate-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
AuthenticatedMedusaRequest,
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework"
import { ConfigModule, IAuthModuleService } from "@medusajs/types"
import {
ContainerRegistrationKeys,
ModuleRegistrationName,
} from "@medusajs/utils"
import { 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 } = 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

let verified: JwtPayload | null = null

try {
verified = verify(token as string, http.jwtSecret!) as JwtPayload
} catch (error) {
return res.status(401).json({ message: "Invalid token" })
}

if (!verified || !verified.provider_identity_id) {
return res.status(401).json({ message: "Invalid token" })
}

const authModule = req.scope.resolve<IAuthModuleService>(
ModuleRegistrationName.AUTH
)

try {
const providerIdentity = await authModule.retrieveProviderIdentity(
verified.provider_identity_id,
{
select: ["auth_identity_id", "entity_id"],
}
)

req_.auth_context = {
actor_type,
auth_identity_id: providerIdentity.auth_identity_id!,
actor_id: providerIdentity.entity_id,
app_metadata: {},
}
} catch (error) {
return res.status(401).json({ message: "Unauthorized" })
}

return next()
}
}
31 changes: 18 additions & 13 deletions packages/modules/auth/src/services/auth-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,34 +242,39 @@ export default class AuthModuleService
}

async generateToken(
payload: {
entity_id: string
provider: string
actor_type: string
} & Record<string, unknown>
entityId: string,
provider: string,
options: {
secret: string
expiry?: number
}
): Promise<string> {
if (!payload.entity_id) {
if (!entityId) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Identifier `entity_id` is required to generate a token"
)
}

if (!options.secret) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Secret `options.secret` is required to generate a token"
)
}

const [providerIdentity] = await this.providerIdentityService_.list({
entity_id: payload.entity_id,
provider: payload.provider,
entity_id: entityId,
provider,
})

const tokenPayload = {
provider_identity_id: providerIdentity.id,
actor_type: payload.actor_type,
}

console.log("TOKEN PAYLOAD", tokenPayload)

// TODO: Add config to auth module
const expiry = DEFAULT_RESET_PASSWORD_TOKEN_DURATION
const token = jwt.sign(tokenPayload, "secret", {
const expiry = options.expiry ?? DEFAULT_RESET_PASSWORD_TOKEN_DURATION
const token = jwt.sign(tokenPayload, options.secret ?? "secret", {
expiresIn: expiry,
})

Expand Down

0 comments on commit 5977d22

Please sign in to comment.