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:
@@ -10,13 +10,13 @@ export class Auth {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
public login = async (
|
||||
scope: "admin" | "store",
|
||||
login = async (
|
||||
actor: "customer" | "user",
|
||||
method: "emailpass",
|
||||
payload: { email: string; password: string }
|
||||
) => {
|
||||
const { token } = await this.client.fetch<{ token: string }>(
|
||||
`/auth/${scope}/${method}`,
|
||||
`/auth/${actor}/${method}`,
|
||||
{
|
||||
method: "POST",
|
||||
body: payload,
|
||||
@@ -45,11 +45,11 @@ export class Auth {
|
||||
}
|
||||
|
||||
create = async (
|
||||
scope: "admin" | "store",
|
||||
actor: "customer" | "user",
|
||||
method: "emailpass",
|
||||
payload: { email: string; password: string }
|
||||
): Promise<{ token: string }> => {
|
||||
return await this.client.fetch(`/auth/${scope}/${method}`, {
|
||||
return await this.client.fetch(`/auth/${actor}/${method}`, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
})
|
||||
|
||||
@@ -22,12 +22,6 @@ export type AuthIdentityDTO = {
|
||||
*/
|
||||
entity_id: string
|
||||
|
||||
/**
|
||||
* The scope of the auth identity. For example,
|
||||
* `admin` or `store`.
|
||||
*/
|
||||
scope: string
|
||||
|
||||
/**
|
||||
* Holds custom data related to the provider in key-value pairs.
|
||||
*/
|
||||
@@ -37,11 +31,6 @@ export type AuthIdentityDTO = {
|
||||
* Holds custom data related to the user in key-value pairs.
|
||||
*/
|
||||
user_metadata: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Holds custom data related to the third-party app in key-value pairs.
|
||||
*/
|
||||
app_metadata: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,12 +56,6 @@ export type CreateAuthIdentityDTO = {
|
||||
*/
|
||||
entity_id: string
|
||||
|
||||
/**
|
||||
* The scope of the auth identity. For example,
|
||||
* `admin` or `store`.
|
||||
*/
|
||||
scope: string
|
||||
|
||||
/**
|
||||
* Holds custom data related to the provider in key-value pairs.
|
||||
*/
|
||||
@@ -82,11 +65,6 @@ export type CreateAuthIdentityDTO = {
|
||||
* Holds custom data related to the user in key-value pairs.
|
||||
*/
|
||||
user_metadata?: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Holds custom data related to the third-party app in key-value pairs.
|
||||
*/
|
||||
app_metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,11 +87,6 @@ export type UpdateAuthIdentityDTO = {
|
||||
* Holds custom data related to the user in key-value pairs.
|
||||
*/
|
||||
user_metadata?: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Holds custom data related to the third-party app in key-value pairs.
|
||||
*/
|
||||
app_metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,31 +42,6 @@ export type AuthenticationResponse = {
|
||||
successRedirectUrl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The configurations of the `providers` option
|
||||
* passed to the Auth Module.
|
||||
*/
|
||||
export type AuthModuleProviderConfig = {
|
||||
/**
|
||||
* The provider's name.
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* The scopes configuration of that provider.
|
||||
*/
|
||||
scopes: Record<string, AuthProviderScope>
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The scope configurations of an auth provider.
|
||||
*/
|
||||
export type AuthProviderScope = Record<string, unknown>
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
@@ -77,30 +52,25 @@ export type AuthenticationInput = {
|
||||
/**
|
||||
* URL of the incoming authentication request.
|
||||
*/
|
||||
url: string
|
||||
url?: string
|
||||
|
||||
/**
|
||||
* Headers of incoming authentication request.
|
||||
*/
|
||||
headers: Record<string, string>
|
||||
headers?: Record<string, string>
|
||||
|
||||
/**
|
||||
* Query params of the incoming authentication request.
|
||||
*/
|
||||
query: Record<string, string>
|
||||
query?: Record<string, string>
|
||||
|
||||
/**
|
||||
* Body of the incoming authentication request.
|
||||
*/
|
||||
body: Record<string, string>
|
||||
|
||||
/**
|
||||
* Scope for the authentication request.
|
||||
*/
|
||||
authScope: string
|
||||
body?: Record<string, string>
|
||||
|
||||
/**
|
||||
* Protocol of the incoming authentication request (For example, `https`).
|
||||
*/
|
||||
protocol: string
|
||||
protocol?: string
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from "./service"
|
||||
export * from "./common"
|
||||
export * from "./provider"
|
||||
export * from "./providers"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
AuthIdentityDTO,
|
||||
AuthenticationInput,
|
||||
AuthenticationResponse,
|
||||
CreateAuthIdentityDTO,
|
||||
} from "./common"
|
||||
|
||||
export interface AuthIdentityProviderService {
|
||||
// The provider is injected by the auth identity module
|
||||
retrieve: (selector: {
|
||||
entity_id: string
|
||||
provider: string
|
||||
}) => Promise<AuthIdentityDTO>
|
||||
create: (data: CreateAuthIdentityDTO) => Promise<AuthIdentityDTO>
|
||||
}
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* Authentication provider interface for the auth module.
|
||||
*
|
||||
*/
|
||||
export interface IAuthProvider {
|
||||
authenticate(
|
||||
data: AuthenticationInput,
|
||||
authIdentityProviderService: AuthIdentityProviderService
|
||||
): Promise<AuthenticationResponse>
|
||||
validateCallback(
|
||||
data: AuthenticationInput,
|
||||
authIdentityProviderService: AuthIdentityProviderService
|
||||
): Promise<AuthenticationResponse>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface EmailPassAuthProviderOptions {
|
||||
hashConfig?: {
|
||||
logN: number
|
||||
r: number
|
||||
p: number
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./emailpass"
|
||||
@@ -43,7 +43,6 @@ export interface IAuthModuleService extends IModuleService {
|
||||
* headers: req.headers,
|
||||
* query: req.query,
|
||||
* body: req.body,
|
||||
* authScope: "admin",
|
||||
* protocol: req.protocol,
|
||||
* } as AuthenticationInput)
|
||||
* ```
|
||||
@@ -81,7 +80,6 @@ export interface IAuthModuleService extends IModuleService {
|
||||
* headers: req.headers,
|
||||
* query: req.query,
|
||||
* body: req.body,
|
||||
* authScope: "admin",
|
||||
* protocol: req.protocol,
|
||||
* } as AuthenticationInput)
|
||||
* ```
|
||||
@@ -200,12 +198,10 @@ export interface IAuthModuleService extends IModuleService {
|
||||
* {
|
||||
* provider: "emailpass",
|
||||
* entity_id: "user@example.com",
|
||||
* scope: "admin",
|
||||
* },
|
||||
* {
|
||||
* provider: "google",
|
||||
* entity_id: "user@gmail.com",
|
||||
* scope: "email profile",
|
||||
* },
|
||||
* ])
|
||||
*/
|
||||
@@ -225,7 +221,6 @@ export interface IAuthModuleService extends IModuleService {
|
||||
* const authIdentity = await authModuleService.create({
|
||||
* provider: "emailpass",
|
||||
* entity_id: "user@example.com",
|
||||
* scope: "admin",
|
||||
* })
|
||||
*/
|
||||
create(
|
||||
@@ -244,9 +239,6 @@ export interface IAuthModuleService extends IModuleService {
|
||||
* const authIdentities = await authModuleService.update([
|
||||
* {
|
||||
* id: "authusr_123",
|
||||
* app_metadata: {
|
||||
* test: true,
|
||||
* },
|
||||
* },
|
||||
* ])
|
||||
*/
|
||||
@@ -265,9 +257,6 @@ export interface IAuthModuleService extends IModuleService {
|
||||
* @example
|
||||
* const authIdentity = await authModuleService.update({
|
||||
* id: "authusr_123",
|
||||
* app_metadata: {
|
||||
* test: true,
|
||||
* },
|
||||
* })
|
||||
*/
|
||||
update(
|
||||
|
||||
@@ -705,6 +705,35 @@ export type ProjectConfigOptions = {
|
||||
* ```
|
||||
*/
|
||||
adminCors: string
|
||||
|
||||
/**
|
||||
* Optionally you can specify the supported authentication providers per actor type (such as user, customer, or any custom actors).
|
||||
* For example, you only want to allow SSO logins for `users` to the admin, while you want to allow email/password logins for `customers` to the storefront.
|
||||
*
|
||||
* `authMethodsPerActor` is a a map where the actor type (eg. 'user') is the key, and an array of supported auth providers as the value.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* Some example values of common use cases:
|
||||
*
|
||||
* Then, set the configuration in `medusa-config.js`:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* http: {
|
||||
* authMethodsPerActor: {
|
||||
* user: ['sso'],
|
||||
* customer: ["emailpass", "google"]
|
||||
* },
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
authMethodsPerActor: Record<string, string[]>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { AuthProviderScope, AuthenticationResponse } from "@medusajs/types"
|
||||
import {
|
||||
AuthIdentityProviderService,
|
||||
AuthenticationInput,
|
||||
AuthenticationResponse,
|
||||
IAuthProvider,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { MedusaError } from "../common"
|
||||
|
||||
export abstract class AbstractAuthModuleProvider {
|
||||
public static PROVIDER: string
|
||||
public static DISPLAY_NAME: string
|
||||
export abstract class AbstractAuthModuleProvider implements IAuthProvider {
|
||||
private static PROVIDER: string
|
||||
private static DISPLAY_NAME: string
|
||||
|
||||
protected readonly container_: any
|
||||
protected scopeConfig_: AuthProviderScope
|
||||
protected scope_: string
|
||||
|
||||
private readonly scopes_: Record<string, AuthProviderScope>
|
||||
|
||||
public get provider() {
|
||||
return (this.constructor as typeof AbstractAuthModuleProvider).PROVIDER
|
||||
}
|
||||
@@ -20,43 +18,22 @@ export abstract class AbstractAuthModuleProvider {
|
||||
return (this.constructor as typeof AbstractAuthModuleProvider).DISPLAY_NAME
|
||||
}
|
||||
|
||||
protected constructor(
|
||||
{ scopes },
|
||||
config: { provider: string; displayName: string }
|
||||
) {
|
||||
protected constructor({}, config: { provider: string; displayName: string }) {
|
||||
this.container_ = arguments[0]
|
||||
this.scopes_ = scopes
|
||||
;(this.constructor as typeof AbstractAuthModuleProvider).PROVIDER ??=
|
||||
config.provider
|
||||
;(this.constructor as typeof AbstractAuthModuleProvider).DISPLAY_NAME ??=
|
||||
config.displayName
|
||||
}
|
||||
|
||||
private validateScope(scope) {
|
||||
if (!this.scopes_[scope]) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
`Scope "${scope}" is not valid for provider ${this.provider}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public withScope(scope: string) {
|
||||
this.validateScope(scope)
|
||||
|
||||
const cloned = new (this.constructor as any)(this.container_)
|
||||
cloned.scope_ = scope
|
||||
cloned.scopeConfig_ = this.scopes_[scope]
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
abstract authenticate(
|
||||
data: Record<string, unknown>
|
||||
data: AuthenticationInput,
|
||||
authIdentityProviderService: AuthIdentityProviderService
|
||||
): Promise<AuthenticationResponse>
|
||||
|
||||
public validateCallback(
|
||||
data: Record<string, unknown>
|
||||
validateCallback(
|
||||
data: AuthenticationInput,
|
||||
authIdentityProviderService: AuthIdentityProviderService
|
||||
): Promise<AuthenticationResponse> {
|
||||
throw new Error(
|
||||
`Callback authentication not implemented for provider ${this.provider}`
|
||||
|
||||
Reference in New Issue
Block a user