Feat(auth): Rename authentication to auth (#6229)

**What**
- rename `authenticationModule` -> `authModule`
This commit is contained in:
Philip Korsholm
2024-01-29 10:19:30 +00:00
committed by GitHub
parent a41aad4bea
commit 512b041929
70 changed files with 245 additions and 147 deletions
@@ -0,0 +1,64 @@
import { AbstractAuthModuleProvider, isString } from "@medusajs/utils"
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
import { AuthUserService } from "@services"
import Scrypt from "scrypt-kdf"
class EmailPasswordProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "emailpass"
public static DISPLAY_NAME = "Email/Password Authentication"
protected readonly authUserSerivce_: AuthUserService
constructor({ authUserService }: { authUserService: AuthUserService }) {
super(arguments[0])
this.authUserSerivce_ = authUserService
}
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",
}
}
const authUser = await this.authUserSerivce_.retrieveByProviderAndEntityId(
email,
EmailPasswordProvider.PROVIDER
)
const password_hash = authUser.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 authUser.provider_metadata!.password
return { success, authUser: JSON.parse(JSON.stringify(authUser)) }
}
}
return {
success: false,
error: "Invalid email or password",
}
}
}
export default EmailPasswordProvider
+226
View File
@@ -0,0 +1,226 @@
import {
AbstractAuthModuleProvider,
MedusaError,
} from "@medusajs/utils"
import {
AuthProviderScope,
AuthenticationInput,
AuthenticationResponse,
} from "@medusajs/types"
import { AuthProviderService, AuthUserService } from "@services"
import jwt, { JwtPayload } from "jsonwebtoken"
import { AuthorizationCode } from "simple-oauth2"
import url from "url"
type InjectedDependencies = {
authUserService: AuthUserService
authProviderService: AuthProviderService
}
type ProviderConfig = {
clientID: string
clientSecret: string
callbackURL: string
}
class GoogleProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "google"
public static DISPLAY_NAME = "Google Authentication"
protected readonly authUserSerivce_: AuthUserService
protected readonly authProviderService_: AuthProviderService
constructor({ authUserService, authProviderService }: InjectedDependencies) {
super(arguments[0])
this.authUserSerivce_ = authUserService
this.authProviderService_ = authProviderService
}
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 {
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 }
}
const code = req.query?.code ?? req.body?.code
return await this.validateCallbackToken(code, req.scope, config)
}
// abstractable
async verify_(refreshToken: string, scope: string) {
const jwtData = (await jwt.decode(refreshToken, {
complete: true,
})) as JwtPayload
const entity_id = jwtData.payload.email
let authUser
try {
authUser = await this.authUserSerivce_.retrieveByProviderAndEntityId(
entity_id,
GoogleProvider.PROVIDER
)
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
authUser = await this.authUserSerivce_.create([
{
entity_id,
provider_id: GoogleProvider.PROVIDER,
user_metadata: jwtData!.payload,
app_metadata: { scope },
},
])
} else {
return { success: false, error: error.message }
}
}
return { success: true, authUser }
}
// abstractable
private async validateCallbackToken(
code: string,
scope: 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, scope)
} catch (error) {
return { success: false, error: error.message }
}
}
private getConfigFromScope(config: AuthProviderScope): ProviderConfig {
const providerConfig: Partial<ProviderConfig> = {}
if (config.clientId) {
providerConfig.clientID = config.clientId
} else {
throw new Error("Google clientID is required")
}
if (config.clientSecret) {
providerConfig.clientSecret = config.clientSecret
} else {
throw new Error("Google clientSecret is required")
}
if (config.callbackURL) {
providerConfig.callbackURL = config.callbackUrl
} else {
throw new Error("Google callbackUrl is required")
}
return providerConfig as ProviderConfig
}
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> {
await this.authProviderService_.retrieve(GoogleProvider.PROVIDER)
const scopeConfig = this.scopes_[req.scope]
const config = this.getConfigFromScope(scopeConfig)
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 })
const location = client.authorizeURL({
redirect_uri: callbackURL,
scope: "email profile",
})
return { success: true, location }
}
private getAuthorizationCodeHandler({
clientID,
clientSecret,
}: {
clientID: string
clientSecret: string
}) {
const config = {
client: {
id: clientID,
secret: clientSecret,
},
auth: {
// TODO: abstract to not be google specific
authorizeHost: "https://accounts.google.com",
authorizePath: "/o/oauth2/v2/auth",
tokenHost: "https://www.googleapis.com",
tokenPath: "/oauth2/v4/token",
},
}
return new AuthorizationCode(config)
}
}
export default GoogleProvider
+2
View File
@@ -0,0 +1,2 @@
export { default as EmailPasswordProvider } from "./email-password"
export { default as GoogleProvider } from "./google"