fix(auth-emailpass): better handle identity with same email error (#13537)
* fix(auth-emailpass): better handle identity with same email error * add test * Create blue-laws-argue.md * check for empty object * trueeee * nit * flip condition
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/auth-emailpass": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(auth-emailpass): better handle identity with same email error
|
||||||
+74
-1
@@ -1,6 +1,7 @@
|
|||||||
import { MedusaError } from "@medusajs/framework/utils"
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
import Scrypt from "scrypt-kdf"
|
import Scrypt from "scrypt-kdf"
|
||||||
import { EmailPassAuthService } from "../../src/services/emailpass"
|
import { EmailPassAuthService } from "../../src/services/emailpass"
|
||||||
|
|
||||||
jest.setTimeout(100000)
|
jest.setTimeout(100000)
|
||||||
|
|
||||||
describe("Email password auth provider", () => {
|
describe("Email password auth provider", () => {
|
||||||
@@ -152,11 +153,83 @@ describe("Email password auth provider", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throw if auth identity with email already exists", async () => {
|
it("updates identity if it exists but doesnt have app_metadata", async () => {
|
||||||
const authServiceSpies = {
|
const authServiceSpies = {
|
||||||
retrieve: jest.fn().mockImplementation(() => {
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
return { success: true }
|
return { success: true }
|
||||||
}),
|
}),
|
||||||
|
update: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
|
entity_id: "test@admin.com",
|
||||||
|
provider: "emailpass",
|
||||||
|
provider_metadata: {
|
||||||
|
password: "somehash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await emailpassService.register(
|
||||||
|
{ body: { email: "test@admin.com", password: "test" } },
|
||||||
|
authServiceSpies
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
||||||
|
expect(authServiceSpies.update).toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(resp.authIdentity?.provider_identities?.[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
entity_id: "test@admin.com",
|
||||||
|
provider_metadata: {},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates identity if it exists but app_metadata is empty", async () => {
|
||||||
|
const authServiceSpies = {
|
||||||
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
|
return { success: true, app_metadata: {} }
|
||||||
|
}),
|
||||||
|
update: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
|
entity_id: "test@admin.com",
|
||||||
|
provider: "emailpass",
|
||||||
|
provider_metadata: {
|
||||||
|
password: "somehash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await emailpassService.register(
|
||||||
|
{ body: { email: "test@admin.com", password: "test" } },
|
||||||
|
authServiceSpies
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
||||||
|
expect(authServiceSpies.update).toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(resp.authIdentity?.provider_identities?.[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
entity_id: "test@admin.com",
|
||||||
|
provider_metadata: {},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throw if auth identity with email already exists and has app_metadata", async () => {
|
||||||
|
const authServiceSpies = {
|
||||||
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
|
return { success: true, app_metadata: {"user_id": "some-id"} }
|
||||||
|
}),
|
||||||
create: jest.fn().mockImplementation(() => {
|
create: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
provider_identities: [
|
provider_identities: [
|
||||||
|
|||||||
@@ -6,17 +6,20 @@ import {
|
|||||||
EmailPassAuthProviderOptions,
|
EmailPassAuthProviderOptions,
|
||||||
Logger,
|
Logger,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import { AbstractAuthModuleProvider, isString, MedusaError, } from "@medusajs/framework/utils"
|
||||||
AbstractAuthModuleProvider,
|
|
||||||
isString,
|
|
||||||
MedusaError,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import Scrypt from "scrypt-kdf"
|
import Scrypt from "scrypt-kdf"
|
||||||
|
import { isPresent } from "@medusajs/utils"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AuthIdentityParams = {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
authIdentityService: AuthIdentityProviderService
|
||||||
|
}
|
||||||
|
|
||||||
interface LocalServiceConfig extends EmailPassAuthProviderOptions {}
|
interface LocalServiceConfig extends EmailPassAuthProviderOptions {}
|
||||||
|
|
||||||
export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
||||||
@@ -79,25 +82,6 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async createAuthIdentity({ email, password, authIdentityService }) {
|
|
||||||
const passwordHash = await this.hashPassword(password)
|
|
||||||
|
|
||||||
const createdAuthIdentity = await authIdentityService.create({
|
|
||||||
entity_id: email,
|
|
||||||
provider_metadata: {
|
|
||||||
password: passwordHash,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const copy = JSON.parse(JSON.stringify(createdAuthIdentity))
|
|
||||||
const providerIdentity = copy.provider_identities?.find(
|
|
||||||
(pi) => pi.provider === this.provider
|
|
||||||
)!
|
|
||||||
delete providerIdentity.provider_metadata?.password
|
|
||||||
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
|
|
||||||
async authenticate(
|
async authenticate(
|
||||||
userData: AuthenticationInput,
|
userData: AuthenticationInput,
|
||||||
authIdentityService: AuthIdentityProviderService
|
authIdentityService: AuthIdentityProviderService
|
||||||
@@ -185,17 +169,31 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authIdentityService.retrieve({
|
const identity = await authIdentityService.retrieve({
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// If app_metadata is not defined or empty, it means no actor was assigned to the auth_identity yet (still "claimable")
|
||||||
|
if (!isPresent(identity.app_metadata)) {
|
||||||
|
const updatedAuthIdentity = await this.upsertAuthIdentity('update', {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
authIdentityService,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
authIdentity: updatedAuthIdentity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Identity with email already exists",
|
error: "Identity with email already exists",
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.type === MedusaError.Types.NOT_FOUND) {
|
if (error.type === MedusaError.Types.NOT_FOUND) {
|
||||||
const createdAuthIdentity = await this.createAuthIdentity({
|
const createdAuthIdentity = await this.upsertAuthIdentity('create', {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
authIdentityService,
|
authIdentityService,
|
||||||
@@ -210,4 +208,27 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async upsertAuthIdentity(type: 'update' | 'create', { email, password, authIdentityService }: AuthIdentityParams) {
|
||||||
|
const passwordHash = await this.hashPassword(password)
|
||||||
|
|
||||||
|
const authIdentity = type === 'create' ? await authIdentityService.create({
|
||||||
|
entity_id: email,
|
||||||
|
provider_metadata: {
|
||||||
|
password: passwordHash,
|
||||||
|
},
|
||||||
|
}) : await authIdentityService.update(email, {
|
||||||
|
provider_metadata: {
|
||||||
|
password: passwordHash,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const copy = JSON.parse(JSON.stringify(authIdentity))
|
||||||
|
const providerIdentity = copy.provider_identities?.find(
|
||||||
|
(pi) => pi.provider === this.provider
|
||||||
|
)!
|
||||||
|
delete providerIdentity.provider_metadata?.password
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user