feat: Separate registration from authentication in auth domain (#8683)

* wip

* feat: Introduce register

* fix: user command

* fix: Invite HTTP tests

* fix: Auth tests

* fix: Invite modules tests
This commit is contained in:
Oli Juhl
2024-08-27 13:44:52 +02:00
committed by GitHub
parent c6eba80af6
commit c11ef01c15
21 changed files with 459 additions and 152 deletions

View File

@@ -136,7 +136,7 @@ describe("Email password auth provider", () => {
}),
}
const resp = await emailpassService.authenticate(
const resp = await emailpassService.register(
{ body: { email: "test@admin.com", password: "test" } },
authServiceSpies
)
@@ -151,4 +151,52 @@ describe("Email password auth provider", () => {
})
)
})
it("throw if auth identity with email already exists", async () => {
const authServiceSpies = {
retrieve: jest.fn().mockImplementation(() => {
return { success: true }
}),
create: 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(resp.error).toEqual("Identity with email already exists")
})
it("throws if auth identity with email doesn't exist", async () => {
const authServiceSpies = {
retrieve: jest.fn().mockImplementation(() => {
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Not found")
}),
create: jest.fn().mockImplementation(() => {}),
}
const resp = await emailpassService.authenticate(
{ body: { email: "test@admin.com", password: "test" } },
authServiceSpies
)
expect(authServiceSpies.retrieve).toHaveBeenCalled()
expect(resp.error).toEqual("Invalid email or password")
})
})

View File

@@ -1,15 +1,15 @@
import {
Logger,
EmailPassAuthProviderOptions,
AuthenticationResponse,
AuthenticationInput,
AuthIdentityProviderService,
AuthenticationResponse,
AuthIdentityDTO,
AuthIdentityProviderService,
EmailPassAuthProviderOptions,
Logger,
} from "@medusajs/types"
import {
AbstractAuthModuleProvider,
MedusaError,
isString,
MedusaError,
} from "@medusajs/utils"
import Scrypt from "scrypt-kdf"
@@ -35,6 +35,26 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
this.logger_ = logger
}
protected async createAuthIdentity({ email, password, authIdentityService }) {
const hashConfig = this.config_.hashConfig ?? { logN: 15, r: 8, p: 1 }
const passwordHash = await Scrypt.kdf(password, hashConfig)
const createdAuthIdentity = await authIdentityService.create({
entity_id: email,
provider_metadata: {
password: passwordHash.toString("base64"),
},
})
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(
userData: AuthenticationInput,
authIdentityService: AuthIdentityProviderService
@@ -54,6 +74,7 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
error: "Email should be a string",
}
}
let authIdentity: AuthIdentityDTO | undefined
try {
@@ -62,25 +83,9 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
})
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
const config = this.config_.hashConfig ?? { logN: 15, r: 8, p: 1 }
const passwordHash = await Scrypt.kdf(password, config)
const createdAuthIdentity = await authIdentityService.create({
entity_id: email,
provider_metadata: {
password: passwordHash.toString("base64"),
},
})
const copy = JSON.parse(JSON.stringify(createdAuthIdentity))
const providerIdentity = copy.provider_identities?.find(
(pi) => pi.provider === this.provider
)!
delete providerIdentity.provider_metadata?.password
return {
success: true,
authIdentity: copy,
success: false,
error: "Invalid email or password",
}
}
@@ -115,4 +120,51 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
error: "Invalid email or password",
}
}
async register(
userData: AuthenticationInput,
authIdentityService: AuthIdentityProviderService
): Promise<AuthenticationResponse> {
const { email, password } = userData.body ?? {}
if (!password || !isString(password)) {
return {
success: false,
error: "Password should be a string",
}
}
if (!email || !isString(email)) {
return {
success: false,
error: "Email should be a string",
}
}
try {
await authIdentityService.retrieve({
entity_id: email,
})
return {
success: false,
error: "Identity with email already exists",
}
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
const createdAuthIdentity = await this.createAuthIdentity({
email,
password,
authIdentityService,
})
return {
success: true,
authIdentity: createdAuthIdentity,
}
}
return { success: false, error: error.message }
}
}
}

View File

@@ -1,9 +1,9 @@
import {
Logger,
GoogleAuthProviderOptions,
AuthenticationResponse,
AuthenticationInput,
AuthenticationResponse,
AuthIdentityProviderService,
GoogleAuthProviderOptions,
Logger,
} from "@medusajs/types"
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
import jwt, { JwtPayload } from "jsonwebtoken"
@@ -29,6 +29,13 @@ export class GoogleAuthService extends AbstractAuthModuleProvider {
this.logger_ = logger
}
async register(_): Promise<AuthenticationResponse> {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Google does not support registration. Use method `authenticate` instead."
)
}
async authenticate(
req: AuthenticationInput
): Promise<AuthenticationResponse> {