chore: Add actor type check + more tests (#10005)

This commit is contained in:
Oli Juhl authored and GitHub committed 2024-11-10 21:38:19 +00:00
1 parent c7b24eb3ec
commit 094971775b
5 files changed
+140 -10

No files matched your search

@@ -230,6 +230,57 @@ medusaIntegrationTestRunner({
expect(login.data).toEqual({ token: expect.any(String) }) expect(login.data).toEqual({ token: expect.any(String) })
}) })
it("should ensure you can only update 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",
actorType: "user",
provider: "emailpass",
secret: "test",
},
})
const response = await api.post(
`/auth/user/emailpass/update?token=${result}`,
{
email: "test+new@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+new@medusa-commerce.com",
password: "new_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 () => { it("should fail if token has expired", async () => {
jest.useFakeTimers() jest.useFakeTimers()
@@ -286,6 +337,76 @@ medusaIntegrationTestRunner({
expect(response.response.status).toEqual(401) expect(response.response.status).toEqual(401)
expect(response.response.data.message).toEqual("Invalid token") expect(response.response.data.message).toEqual("Invalid token")
}) })
it("should fail if update is attempted on different actor type", 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",
actorType: "user",
provider: "emailpass",
secret: "test",
},
})
// Advance time by 15 minutes
jest.advanceTimersByTime(15 * 60 * 1000)
const response = await api
.post(`/auth/customer/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")
})
it("should fail if token secret is incorrect", 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",
actorType: "user",
provider: "emailpass",
secret: "incorrect_secret",
},
})
// 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")
})
}) })
it("should refresh the token successfully", async () => { it("should refresh the token successfully", async () => {
@@ -45,6 +45,7 @@ export const generateResetPasswordTokenWorkflow = createWorkflow(
{ {
entity_id: input.entityId, entity_id: input.entityId,
provider: input.provider, provider: input.provider,
actor_type: input.actorType,
}, },
{ {
secret: input.secret, secret: input.secret,
@@ -13,9 +13,14 @@ export const POST = async (
const authService = req.scope.resolve<IAuthModuleService>(Modules.AUTH) const authService = req.scope.resolve<IAuthModuleService>(Modules.AUTH)
const updateData = {
...(req.body as Record<string, unknown>),
entity_id: req.auth_context.actor_id, // comes from the validated token
}
const { authIdentity, success, error } = await authService.updateProvider( const { authIdentity, success, error } = await authService.updateProvider(
auth_provider, auth_provider,
req.body as Record<string, unknown> updateData
) )
if (success && authIdentity) { if (success && authIdentity) {
@@ -46,6 +46,11 @@ export const validateToken = () => {
return next(errorObject) return next(errorObject)
} }
// E.g. token was requested for a customer, but attempted used for a user
if (decoded?.actor_type !== actor_type) {
return next(errorObject)
}
const [providerIdentity] = await authModule.listProviderIdentities( const [providerIdentity] = await authModule.listProviderIdentities(
{ {
entity_id: decoded.entity_id, entity_id: decoded.entity_id,
@@ -60,17 +65,15 @@ export const validateToken = () => {
return next(errorObject) return next(errorObject)
} }
let verified: JwtPayload | null = null
try { try {
verified = verify(token as string, http.jwtSecret as string) as JwtPayload verify(token as string, http.jwtSecret as string) as JwtPayload
} catch (error) { } catch (error) {
return next(errorObject) return next(errorObject)
} }
req_.auth_context = { req_.auth_context = {
actor_type, actor_type,
auth_identity_id: verified.auth_identity_id!, auth_identity_id: providerIdentity.auth_identity_id!,
actor_id: providerIdentity.entity_id, actor_id: providerIdentity.entity_id,
app_metadata: {}, app_metadata: {},
} }
@@ -43,15 +43,15 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
} }
async update( async update(
data: { email: string; password: string }, data: { password: string; entity_id: string },
authIdentityService: AuthIdentityProviderService authIdentityService: AuthIdentityProviderService
) { ) {
const { email, password } = data ?? {} const { password, entity_id } = data ?? {}
if (!email || !isString(email)) { if (!entity_id) {
return { return {
success: false, success: false,
error: `Cannot update ${this.provider} provider identity without email`, error: `Cannot update ${this.provider} provider identity without entity_id`,
} }
} }
@@ -64,7 +64,7 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
try { try {
const passwordHash = await this.hashPassword(password) const passwordHash = await this.hashPassword(password)
authIdentity = await authIdentityService.update(email, { authIdentity = await authIdentityService.update(entity_id, {
provider_metadata: { provider_metadata: {
password: passwordHash, password: passwordHash,
}, },