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
+5 -5
View File
@@ -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
}
+2
View File
@@ -1,2 +1,4 @@
export * from "./service"
export * from "./common"
export * from "./provider"
export * from "./providers"
+32
View File
@@ -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"
-11
View File
@@ -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}`