Revamp the authentication setup (#7419)

* feat: Add email pass authentication provider package

* feat: Revamp auth module and remove concept of scope

* feat: Revamp the auth module to be more standardized in how providers are loaded

* feat: Switch from scope to actor type for authentication

* feat: Add support for per-actor auth methods

* feat: Add emailpass auth provider by default

* fix: Add back app_metadata in auth module
This commit is contained in:
Stevche Radevski
2024-05-23 20:56:40 +02:00
committed by GitHub
parent 7b0cfe3b77
commit 8a070d5d85
100 changed files with 991 additions and 1005 deletions

View File

@@ -1,114 +0,0 @@
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
import {
AbstractAuthModuleProvider,
MedusaError,
isString,
} from "@medusajs/utils"
import { AuthIdentityService } from "@services"
import Scrypt from "scrypt-kdf"
class EmailPasswordProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "emailpass"
public static DISPLAY_NAME = "Email/Password Authentication"
protected readonly authIdentitySerivce_: AuthIdentityService
constructor({
authIdentityService,
}: {
authIdentityService: AuthIdentityService
}) {
super(arguments[0], {
provider: EmailPasswordProvider.PROVIDER,
displayName: EmailPasswordProvider.DISPLAY_NAME,
})
this.authIdentitySerivce_ = authIdentityService
}
private getHashConfig() {
const scopeConfig = this.scopeConfig_.hashConfig as
| Scrypt.ScryptParams
| undefined
const defaultHashConfig = { logN: 15, r: 8, p: 1 }
// Return custom defined hash config or default hash parameters
return scopeConfig ?? defaultHashConfig
}
async authenticate(
userData: AuthenticationInput
): 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",
}
}
let authIdentity
try {
authIdentity =
await this.authIdentitySerivce_.retrieveByProviderAndEntityId(
email,
EmailPasswordProvider.PROVIDER
)
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
const password_hash = await Scrypt.kdf(password, this.getHashConfig())
const [createdAuthIdentity] = await this.authIdentitySerivce_.create([
{
entity_id: email,
provider: EmailPasswordProvider.PROVIDER,
scope: this.scope_,
provider_metadata: {
password: password_hash.toString("base64"),
},
},
])
return {
success: true,
authIdentity: JSON.parse(JSON.stringify(createdAuthIdentity)),
}
}
return { success: false, error: error.message }
}
const password_hash = authIdentity.provider_metadata?.password
if (isString(password_hash)) {
const buf = Buffer.from(password_hash as string, "base64")
const success = await Scrypt.verify(buf, password)
if (success) {
delete authIdentity.provider_metadata!.password
return {
success,
authIdentity: JSON.parse(JSON.stringify(authIdentity)),
}
}
}
return {
success: false,
error: "Invalid email or password",
}
}
}
export default EmailPasswordProvider

View File

@@ -1,13 +1,12 @@
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
import { AuthIdentityService } from "@services"
import jwt, { JwtPayload } from "jsonwebtoken"
import { AuthorizationCode } from "simple-oauth2"
import url from "url"
type InjectedDependencies = {
authIdentityService: AuthIdentityService
authIdentityService: any
}
type ProviderConfig = {
@@ -18,15 +17,12 @@ type ProviderConfig = {
}
class GoogleProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "google"
public static DISPLAY_NAME = "Google Authentication"
protected readonly authIdentityService_: any
protected readonly authIdentityService_: AuthIdentityService
constructor({ authIdentityService }: InjectedDependencies) {
constructor({ authIdentityService }: InjectedDependencies, options: any) {
super(arguments[0], {
provider: GoogleProvider.PROVIDER,
displayName: GoogleProvider.DISPLAY_NAME,
provider: "google",
displayName: "Google Authentication",
})
this.authIdentityService_ = authIdentityService
@@ -72,6 +68,9 @@ class GoogleProvider extends AbstractAuthModuleProvider {
}
const code = req.query?.code ?? req.body?.code
if (!code) {
return { success: false, error: "No code provided" }
}
return await this.validateCallbackToken(code, config)
}
@@ -89,16 +88,15 @@ class GoogleProvider extends AbstractAuthModuleProvider {
authIdentity =
await this.authIdentityService_.retrieveByProviderAndEntityId(
entity_id,
GoogleProvider.PROVIDER
this.provider
)
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
const [createdAuthIdentity] = await this.authIdentityService_.create([
{
entity_id,
provider: GoogleProvider.PROVIDER,
provider: this.provider,
user_metadata: jwtData!.payload,
scope: this.scope_,
},
])
authIdentity = createdAuthIdentity
@@ -132,7 +130,7 @@ class GoogleProvider extends AbstractAuthModuleProvider {
accessToken.token.id_token
)
const { successRedirectUrl } = this.getConfigFromScope()
const { successRedirectUrl } = this.getConfig()
return {
success,
@@ -144,8 +142,10 @@ class GoogleProvider extends AbstractAuthModuleProvider {
}
}
private getConfigFromScope(): ProviderConfig {
const config: Partial<ProviderConfig> = { ...this.scopeConfig_ }
private getConfig(): ProviderConfig {
// TODO: Fetch this from provider config
// const config: Partial<ProviderConfig> = { ...this.scopeConfig_ }
const config = {} as any
if (!config.clientID) {
throw new Error("Google clientID is required")
@@ -163,7 +163,7 @@ class GoogleProvider extends AbstractAuthModuleProvider {
}
private originalURL(req: AuthenticationInput) {
const host = req.headers.host
const host = req.headers?.host
const protocol = req.protocol
const path = req.url || ""
@@ -173,7 +173,7 @@ class GoogleProvider extends AbstractAuthModuleProvider {
private async getProviderConfig(
req: AuthenticationInput
): Promise<ProviderConfig> {
const config = this.getConfigFromScope()
const config = this.getConfig()
const callbackURL = config.callbackURL

View File

@@ -1,2 +1 @@
export { default as EmailPasswordProvider } from "./email-password"
export { default as GoogleProvider } from "./google"
export { default as GoogleProvider } from "./google"