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:
1 parent
5a550e73b4
commit
638b47ff70
7 files changed
+126
-87
No files matched your search
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
import { IAuthenticationModuleService } from "@medusajs/types"
|
|
||||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
|
import { IAuthenticationModuleService } from "@medusajs/types"
|
||||||
import { MikroOrmWrapper } from "../../../utils"
|
import { MikroOrmWrapper } from "../../../utils"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
import { IAuthenticationModuleService } from "@medusajs/types"
|
|
||||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
|
import { IAuthenticationModuleService } from "@medusajs/types"
|
||||||
import { MikroOrmWrapper } from "../../../utils"
|
import { MikroOrmWrapper } from "../../../utils"
|
||||||
import Scrypt from "scrypt-kdf"
|
import Scrypt from "scrypt-kdf"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
|||||||
@@ -43,46 +43,29 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
|
|||||||
this.authProviderService_ = authProviderService
|
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(
|
async authenticate(
|
||||||
req: AuthenticationInput
|
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> {
|
): Promise<AuthenticationResponse> {
|
||||||
if (req.query && req.query.error) {
|
if (req.query && req.query.error) {
|
||||||
return {
|
return {
|
||||||
@@ -91,7 +74,7 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let config
|
let config: ProviderConfig
|
||||||
|
|
||||||
try {
|
try {
|
||||||
config = await this.getProviderConfig(req)
|
config = await this.getProviderConfig(req)
|
||||||
@@ -99,43 +82,9 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
|
|||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
|
|
||||||
let { callbackURL, clientID, clientSecret } = config
|
const code = req.query?.code ?? req.body?.code
|
||||||
|
|
||||||
const meta: ProviderConfig = {
|
return await this.validateCallbackToken(code, config)
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// abstractable
|
// abstractable
|
||||||
@@ -169,6 +118,68 @@ class GoogleProvider extends AbstractAuthenticationModuleProvider {
|
|||||||
return { success: true, authUser }
|
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
|
// Abstractable
|
||||||
private getRedirect({ clientID, callbackURL, clientSecret }: ProviderConfig) {
|
private getRedirect({ clientID, callbackURL, clientSecret }: ProviderConfig) {
|
||||||
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
|
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { AuthenticationResponse } from "@medusajs/types"
|
import { AbstractAuthenticationModuleProvider, isString } from "@medusajs/utils"
|
||||||
|
|
||||||
import { AuthUserService } from "@services"
|
import { AuthUserService } from "@services"
|
||||||
|
import { AuthenticationResponse } from "@medusajs/types"
|
||||||
import Scrypt from "scrypt-kdf"
|
import Scrypt from "scrypt-kdf"
|
||||||
import { AbstractAuthenticationModuleProvider, isString } from "@medusajs/utils"
|
|
||||||
|
|
||||||
class UsernamePasswordProvider extends AbstractAuthenticationModuleProvider {
|
class UsernamePasswordProvider extends AbstractAuthenticationModuleProvider {
|
||||||
public static PROVIDER = "usernamePassword"
|
public static PROVIDER = "usernamePassword"
|
||||||
|
|||||||
@@ -368,18 +368,15 @@ export default class AuthenticationModuleService<
|
|||||||
return containerProvider
|
return containerProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
|
||||||
async authenticate(
|
async authenticate(
|
||||||
provider: string,
|
provider: string,
|
||||||
authenticationData: Record<string, unknown>,
|
authenticationData: Record<string, unknown>
|
||||||
@MedusaContext() sharedContext: Context = {}
|
|
||||||
): Promise<AuthenticationResponse> {
|
): Promise<AuthenticationResponse> {
|
||||||
let registeredProvider
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.retrieveAuthProvider(provider, {})
|
await this.retrieveAuthProvider(provider, {})
|
||||||
|
|
||||||
registeredProvider = this.getRegisteredAuthenticationProvider(provider)
|
const registeredProvider =
|
||||||
|
this.getRegisteredAuthenticationProvider(provider)
|
||||||
|
|
||||||
return await registeredProvider.authenticate(authenticationData)
|
return await registeredProvider.authenticate(authenticationData)
|
||||||
} catch (error) {
|
} 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() {
|
private async createProvidersOnLoad() {
|
||||||
const providersToLoad = this.__container__["auth_providers"]
|
const providersToLoad = this.__container__["auth_providers"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AuthenticationResponse,
|
|
||||||
AuthProviderDTO,
|
AuthProviderDTO,
|
||||||
AuthUserDTO,
|
AuthUserDTO,
|
||||||
|
AuthenticationResponse,
|
||||||
CreateAuthProviderDTO,
|
CreateAuthProviderDTO,
|
||||||
CreateAuthUserDTO,
|
CreateAuthUserDTO,
|
||||||
FilterableAuthProviderProps,
|
FilterableAuthProviderProps,
|
||||||
@@ -20,6 +20,11 @@ export interface IAuthenticationModuleService extends IModuleService {
|
|||||||
providerData: Record<string, unknown>
|
providerData: Record<string, unknown>
|
||||||
): Promise<AuthenticationResponse>
|
): Promise<AuthenticationResponse>
|
||||||
|
|
||||||
|
validateCallback(
|
||||||
|
provider: string,
|
||||||
|
providerData: Record<string, unknown>
|
||||||
|
): Promise<AuthenticationResponse>
|
||||||
|
|
||||||
retrieveAuthProvider(
|
retrieveAuthProvider(
|
||||||
provider: string,
|
provider: string,
|
||||||
config?: FindConfig<AuthProviderDTO>,
|
config?: FindConfig<AuthProviderDTO>,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AuthenticationResponse } from "@medusajs/types";
|
import { AuthenticationResponse } from "@medusajs/types"
|
||||||
|
|
||||||
export abstract class AbstractAuthenticationModuleProvider {
|
export abstract class AbstractAuthenticationModuleProvider {
|
||||||
public static PROVIDER: string
|
public static PROVIDER: string
|
||||||
@@ -16,4 +16,12 @@ export abstract class AbstractAuthenticationModuleProvider {
|
|||||||
abstract authenticate(
|
abstract authenticate(
|
||||||
data: Record<string, unknown>
|
data: Record<string, unknown>
|
||||||
): Promise<AuthenticationResponse>
|
): Promise<AuthenticationResponse>
|
||||||
|
|
||||||
|
public validateCallback(
|
||||||
|
data: Record<string, unknown>
|
||||||
|
): Promise<AuthenticationResponse> {
|
||||||
|
throw new Error(
|
||||||
|
`Callback authentication not implemented for provider ${this.provider}`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user