feat(authentication, types): split authenticate method into two (#6184)

* init

* fetch providers using updated string

* update loaders

* add more tests

* add authenticationresponse type

* update types for authentication method

* add entity_id and update provider

* update pr with return type

* create loaders onApplicationStart

* cleanup provider class

* run application start hook before each

* fix pr feedback

* create private onApplicationStart method

* assign repository

* init

* add entity_id and update provider

* initial implementation

* update lockfile

* fix conflicts

* add config variables

* update types

* refactor google provider

* re-order methods

* fix pr feedback p. 1

* add initial type and update callback authorization

* add google provider to integration test

* fix feedback

* initial implementation (#6171)

* initial implementation

* remove oauth lib

* move abstract authentication provider

* shuffle files around

* init

* add entity_id and update provider

* initial implementation

* update lockfile

* fix conflicts

* add config variables

* update types

* refactor google provider

* re-order methods

* fix pr feedback p. 1

* add initial type and update callback authorization

* add google provider to integration test

* fix feedback

* initial implementation (#6171)

* initial implementation

* remove oauth lib

* move abstract authentication provider

* shuffle files around

* Update packages/authentication/src/migrations/Migration20240122041959.ts

Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>

* split authentication methods

* call verify with token

* update integration tests

* feedback

* rename split methods

* fix provider integration test

---------

Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
Philip Korsholm
2024-01-26 11:37:23 +08:00
committed by GitHub
parent 5a550e73b4
commit 638b47ff70
7 changed files with 126 additions and 87 deletions

View File

@@ -1,5 +1,6 @@
import { IAuthenticationModuleService } from "@medusajs/types"
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
import { IAuthenticationModuleService } from "@medusajs/types"
import { MikroOrmWrapper } from "../../../utils"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { createAuthProviders } from "../../../__fixtures__/auth-provider"

View File

@@ -1,5 +1,6 @@
import { IAuthenticationModuleService } from "@medusajs/types"
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
import { IAuthenticationModuleService } from "@medusajs/types"
import { MikroOrmWrapper } from "../../../utils"
import Scrypt from "scrypt-kdf"
import { SqlEntityManager } from "@mikro-orm/postgresql"

View File

@@ -43,46 +43,29 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
this.authProviderService_ = authProviderService
}
private async validateConfig(config: Partial<ProviderConfig>) {
if (!config.clientID) {
throw new Error("Google clientID is required")
}
if (!config.clientSecret) {
throw new Error("Google clientSecret is required")
}
if (!config.callbackURL) {
throw new Error("Google callbackUrl is required")
}
}
private originalURL(req: AuthenticationInput) {
const tls = req.connection.encrypted,
host = req.headers.host,
protocol = tls ? "https" : "http",
path = req.url || ""
return protocol + "://" + host + path
}
async getProviderConfig(req: AuthenticationInput): Promise<ProviderConfig> {
const { config } = (await this.authProviderService_.retrieve(
GoogleProvider.PROVIDER
)) as AuthProvider & { config: ProviderConfig }
this.validateConfig(config || {})
const { callbackURL } = config
const parsedCallbackUrl = !url.parse(callbackURL).protocol
? url.resolve(this.originalURL(req), callbackURL)
: callbackURL
return { ...config, callbackURL: parsedCallbackUrl }
}
async authenticate(
req: AuthenticationInput
): Promise<AuthenticationResponse> {
if (req.query?.error) {
return {
success: false,
error: `${req.query.error_description}, read more at: ${req.query.error_uri}`,
}
}
let config: ProviderConfig
try {
config = await this.getProviderConfig(req)
} catch (error) {
return { success: false, error: error.message }
}
return this.getRedirect(config)
}
async validateCallback(
req: AuthenticationInput
): Promise<AuthenticationResponse> {
if (req.query && req.query.error) {
return {
@@ -91,7 +74,7 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
}
}
let config
let config: ProviderConfig
try {
config = await this.getProviderConfig(req)
@@ -99,43 +82,9 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
return { success: false, error: error.message }
}
let { callbackURL, clientID, clientSecret } = config
const code = req.query?.code ?? req.body?.code
const meta: ProviderConfig = {
clientID,
callbackURL,
clientSecret,
}
const code = (req.query && req.query.code) || (req.body && req.body.code)
// Redirect to google
if (!code) {
return this.getRedirect(meta)
}
return await this.validateCallback(code, meta)
}
// abstractable
private async validateCallback(
code: string,
{ clientID, callbackURL, clientSecret }: ProviderConfig
) {
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
const tokenParams = {
code,
redirect_uri: callbackURL,
}
try {
const accessToken = await client.getToken(tokenParams)
return await this.verify_(accessToken.token.id_token)
} catch (error) {
return { success: false, error: error.message }
}
return await this.validateCallbackToken(code, config)
}
// abstractable
@@ -169,6 +118,68 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
return { success: true, authUser }
}
// abstractable
private async validateCallbackToken(
code: string,
{ clientID, callbackURL, clientSecret }: ProviderConfig
) {
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
const tokenParams = {
code,
redirect_uri: callbackURL,
}
try {
const accessToken = await client.getToken(tokenParams)
return await this.verify_(accessToken.token.id_token)
} catch (error) {
return { success: false, error: error.message }
}
}
private async validateConfig(config: Partial<ProviderConfig>) {
if (!config.clientID) {
throw new Error("Google clientID is required")
}
if (!config.clientSecret) {
throw new Error("Google clientSecret is required")
}
if (!config.callbackURL) {
throw new Error("Google callbackUrl is required")
}
}
private originalURL(req: AuthenticationInput) {
const tls = req.connection.encrypted
const host = req.headers.host
const protocol = tls ? "https" : "http"
const path = req.url || ""
return protocol + "://" + host + path
}
private async getProviderConfig(
req: AuthenticationInput
): Promise<ProviderConfig> {
const { config } = (await this.authProviderService_.retrieve(
GoogleProvider.PROVIDER
)) as AuthProvider & { config: ProviderConfig }
this.validateConfig(config || {})
const { callbackURL } = config
const parsedCallbackUrl = !url.parse(callbackURL).protocol
? url.resolve(this.originalURL(req), callbackURL)
: callbackURL
return { ...config, callbackURL: parsedCallbackUrl }
}
// Abstractable
private getRedirect({ clientID, callbackURL, clientSecret }: ProviderConfig) {
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })

View File

@@ -1,8 +1,8 @@
import { AuthenticationResponse } from "@medusajs/types"
import { AbstractAuthenticationModuleProvider, isString } from "@medusajs/utils"
import { AuthUserService } from "@services"
import { AuthenticationResponse } from "@medusajs/types"
import Scrypt from "scrypt-kdf"
import { AbstractAuthenticationModuleProvider, isString } from "@medusajs/utils"
class UsernamePasswordProvider extends AbstractAuthenticationModuleProvider {
public static PROVIDER = "usernamePassword"

View File

@@ -368,18 +368,15 @@ export default class AuthenticationModuleService<
return containerProvider
}
@InjectTransactionManager("baseRepository_")
async authenticate(
provider: string,
authenticationData: Record<string, unknown>,
@MedusaContext() sharedContext: Context = {}
authenticationData: Record<string, unknown>
): Promise<AuthenticationResponse> {
let registeredProvider
try {
await this.retrieveAuthProvider(provider, {})
registeredProvider = this.getRegisteredAuthenticationProvider(provider)
const registeredProvider =
this.getRegisteredAuthenticationProvider(provider)
return await registeredProvider.authenticate(authenticationData)
} catch (error) {
@@ -387,6 +384,22 @@ export default class AuthenticationModuleService<
}
}
async validateCallback(
provider: string,
authenticationData: Record<string, unknown>
): Promise<AuthenticationResponse> {
try {
await this.retrieveAuthProvider(provider, {})
const registeredProvider =
this.getRegisteredAuthenticationProvider(provider)
return await registeredProvider.validateCallback(authenticationData)
} catch (error) {
return { success: false, error: error.message }
}
}
private async createProvidersOnLoad() {
const providersToLoad = this.__container__["auth_providers"]

View File

@@ -1,7 +1,7 @@
import {
AuthenticationResponse,
AuthProviderDTO,
AuthUserDTO,
AuthenticationResponse,
CreateAuthProviderDTO,
CreateAuthUserDTO,
FilterableAuthProviderProps,
@@ -20,6 +20,11 @@ export interface IAuthenticationModuleService extends IModuleService {
providerData: Record<string, unknown>
): Promise<AuthenticationResponse>
validateCallback(
provider: string,
providerData: Record<string, unknown>
): Promise<AuthenticationResponse>
retrieveAuthProvider(
provider: string,
config?: FindConfig<AuthProviderDTO>,

View File

@@ -1,4 +1,4 @@
import { AuthenticationResponse } from "@medusajs/types";
import { AuthenticationResponse } from "@medusajs/types"
export abstract class AbstractAuthenticationModuleProvider {
public static PROVIDER: string
@@ -16,4 +16,12 @@ export abstract class AbstractAuthenticationModuleProvider {
abstract authenticate(
data: Record<string, unknown>
): Promise<AuthenticationResponse>
public validateCallback(
data: Record<string, unknown>
): Promise<AuthenticationResponse> {
throw new Error(
`Callback authentication not implemented for provider ${this.provider}`
)
}
}