Revamp auth module to support multiple providers linked to a single auth identity (#7521)
This commit is contained in:
1 parent
20cd6a7b51
commit
fafc92b875
23 files changed
+583
-159
No files matched your search
@@ -41,11 +41,15 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
|
|
||||||
await authService.create({
|
await authService.create({
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
provider: "emailpass",
|
provider: "emailpass",
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: passwordHash,
|
password: passwordHash,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await api
|
const response = await api
|
||||||
@@ -72,11 +76,15 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
|
|
||||||
await authService.create({
|
await authService.create({
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
provider: "emailpass",
|
provider: "emailpass",
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: passwordHash,
|
password: passwordHash,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
|
|||||||
@@ -12,30 +12,14 @@ export type AuthIdentityDTO = {
|
|||||||
id: string
|
id: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the provider used to authenticate the user.
|
* The list of provider identities linked to the auth identity.
|
||||||
*/
|
**/
|
||||||
provider: string
|
provider_identities?: ProviderIdentityDTO[]
|
||||||
|
|
||||||
/**
|
|
||||||
* The user's identifier. For example, when using the `emailpass`
|
|
||||||
* provider, the `entity_id` would be the user's email.
|
|
||||||
*/
|
|
||||||
entity_id: string
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds information related to the actor IDs tied to the auth identity.
|
* Holds information related to the actor IDs tied to the auth identity.
|
||||||
*/
|
*/
|
||||||
app_metadata?: Record<string, unknown>
|
app_metadata?: Record<string, unknown>
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds custom data related to the provider in key-value pairs.
|
|
||||||
*/
|
|
||||||
provider_metadata?: Record<string, unknown>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds custom data related to the user in key-value pairs.
|
|
||||||
*/
|
|
||||||
user_metadata: Record<string, unknown>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,31 +34,14 @@ export type CreateAuthIdentityDTO = {
|
|||||||
id?: string
|
id?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the provider used to authenticate
|
* The list of provider identities linked to the auth identity.
|
||||||
* the user.
|
**/
|
||||||
*/
|
provider_identities?: CreateProviderIdentityDTO[]
|
||||||
provider: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user's identifier. For example, when using the `emailpass`
|
|
||||||
* provider, the `entity_id` would be the user's email.
|
|
||||||
*/
|
|
||||||
entity_id: string
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds information related to the actor IDs tied to the auth identity.
|
* Holds information related to the actor IDs tied to the auth identity.
|
||||||
*/
|
*/
|
||||||
app_metadata?: Record<string, unknown>
|
app_metadata?: Record<string, unknown>
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds custom data related to the provider in key-value pairs.
|
|
||||||
*/
|
|
||||||
provider_metadata?: Record<string, unknown>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds custom data related to the user in key-value pairs.
|
|
||||||
*/
|
|
||||||
user_metadata?: Record<string, unknown>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,6 +59,101 @@ export type UpdateAuthIdentityDTO = {
|
|||||||
* Holds information related to the actor IDs tied to the auth identity.
|
* Holds information related to the actor IDs tied to the auth identity.
|
||||||
*/
|
*/
|
||||||
app_metadata?: Record<string, unknown>
|
app_metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* The provider identity details.
|
||||||
|
*/
|
||||||
|
export type ProviderIdentityDTO = {
|
||||||
|
/**
|
||||||
|
* The ID of the provider identity.
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ID of the provider used to authenticate the user.
|
||||||
|
*/
|
||||||
|
provider: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user's identifier. For example, when using the `emailpass`
|
||||||
|
* provider, the `entity_id` would be the user's email.
|
||||||
|
*/
|
||||||
|
entity_id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The auth identity linked to the provider identity.
|
||||||
|
*/
|
||||||
|
auth_identity?: AuthIdentityDTO
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds custom data related to the provider in key-value pairs.
|
||||||
|
*/
|
||||||
|
provider_metadata?: Record<string, unknown>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds custom data related to the user in key-value pairs.
|
||||||
|
*/
|
||||||
|
user_metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* The provider identity to be created.
|
||||||
|
*/
|
||||||
|
export type CreateProviderIdentityDTO = {
|
||||||
|
/**
|
||||||
|
* The ID of the provider identity.
|
||||||
|
*/
|
||||||
|
id?: string
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ID of the provider used to authenticate the user.
|
||||||
|
*/
|
||||||
|
provider: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user's identifier. For example, when using the `emailpass`
|
||||||
|
* provider, the `entity_id` would be the user's email.
|
||||||
|
*/
|
||||||
|
entity_id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The auth identity linked to the provider identity. Needs to be specified if creating a new provider identity directly.
|
||||||
|
*/
|
||||||
|
auth_identity_id?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds custom data related to the provider in key-value pairs.
|
||||||
|
*/
|
||||||
|
provider_metadata?: Record<string, unknown>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds custom data related to the user in key-value pairs.
|
||||||
|
*/
|
||||||
|
user_metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* The provider identity to be created.
|
||||||
|
*/
|
||||||
|
export type UpdateProviderIdentityDTO = {
|
||||||
|
/**
|
||||||
|
* The ID of the provider identity.
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user's identifier. For example, when using the `emailpass`
|
||||||
|
* provider, the `entity_id` would be the user's email.
|
||||||
|
*/
|
||||||
|
entity_id: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds custom data related to the provider in key-value pairs.
|
* Holds custom data related to the provider in key-value pairs.
|
||||||
*/
|
*/
|
||||||
@@ -114,7 +176,17 @@ export interface FilterableAuthIdentityProps
|
|||||||
id?: string[]
|
id?: string[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the auth identities by the ID of their auth provider.
|
* The provider identities to filter the auth identity by.
|
||||||
*/
|
*/
|
||||||
provider?: string[] | string
|
provider_identities?: {
|
||||||
|
/**
|
||||||
|
* Filter the provider identities by the ID of the provider identity ID they are linked to.
|
||||||
|
*/
|
||||||
|
entity_id?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the provider identities by the provider handle.
|
||||||
|
*/
|
||||||
|
provider?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AuthIdentityDTO } from "./auth-identity"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface
|
* @interface
|
||||||
*
|
*
|
||||||
@@ -12,7 +14,7 @@ export type AuthenticationResponse = {
|
|||||||
/**
|
/**
|
||||||
* The authenticated user's details.
|
* The authenticated user's details.
|
||||||
*/
|
*/
|
||||||
authIdentity?: any
|
authIdentity?: AuthIdentityDTO
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If an error occurs during the authentication process,
|
* If an error occurs during the authentication process,
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ import {
|
|||||||
AuthIdentityDTO,
|
AuthIdentityDTO,
|
||||||
AuthenticationInput,
|
AuthenticationInput,
|
||||||
AuthenticationResponse,
|
AuthenticationResponse,
|
||||||
CreateAuthIdentityDTO,
|
|
||||||
} from "./common"
|
} from "./common"
|
||||||
|
|
||||||
|
// This interface currently won't allow for linking multiple providers to a single auth entity. That flow is more complex and not supported yet.
|
||||||
export interface AuthIdentityProviderService {
|
export interface AuthIdentityProviderService {
|
||||||
// The provider is injected by the auth identity module
|
// The provider is injected by the auth identity module
|
||||||
retrieve: (selector: {
|
retrieve: (selector: { entity_id: string }) => Promise<AuthIdentityDTO>
|
||||||
|
create: (data: {
|
||||||
entity_id: string
|
entity_id: string
|
||||||
provider: string
|
provider_metadata?: Record<string, unknown>
|
||||||
|
user_metadata?: Record<string, unknown>
|
||||||
}) => Promise<AuthIdentityDTO>
|
}) => Promise<AuthIdentityDTO>
|
||||||
create: (data: CreateAuthIdentityDTO) => Promise<AuthIdentityDTO>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,13 +10,6 @@ import { Context } from "../shared-context"
|
|||||||
import { FindConfig } from "../common"
|
import { FindConfig } from "../common"
|
||||||
import { IModuleService } from "../modules-sdk"
|
import { IModuleService } from "../modules-sdk"
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
export type JWTGenerationOptions = {
|
|
||||||
expiresIn?: string | number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main service interface for the Auth Module.
|
* The main service interface for the Auth Module.
|
||||||
*/
|
*/
|
||||||
@@ -196,12 +189,16 @@ export interface IAuthModuleService extends IModuleService {
|
|||||||
* @example
|
* @example
|
||||||
* const authIdentities = await authModuleService.create([
|
* const authIdentities = await authModuleService.create([
|
||||||
* {
|
* {
|
||||||
|
* provider_identities: [{
|
||||||
* provider: "emailpass",
|
* provider: "emailpass",
|
||||||
* entity_id: "user@example.com",
|
* entity_id: "user@example.com",
|
||||||
|
* }]
|
||||||
* },
|
* },
|
||||||
* {
|
* {
|
||||||
|
* provider_identities: [{
|
||||||
* provider: "google",
|
* provider: "google",
|
||||||
* entity_id: "user@gmail.com",
|
* entity_id: "user@gmail.com",
|
||||||
|
* }]
|
||||||
* },
|
* },
|
||||||
* ])
|
* ])
|
||||||
*/
|
*/
|
||||||
@@ -219,8 +216,10 @@ export interface IAuthModuleService extends IModuleService {
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const authIdentity = await authModuleService.create({
|
* const authIdentity = await authModuleService.create({
|
||||||
|
* provider_identities: [{
|
||||||
* provider: "emailpass",
|
* provider: "emailpass",
|
||||||
* entity_id: "user@example.com",
|
* entity_id: "user@example.com",
|
||||||
|
* }]
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
create(
|
create(
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
await service.validateCallback(auth_provider, authData)
|
await service.validateCallback(auth_provider, authData)
|
||||||
|
|
||||||
const entityIdKey = `${actor_type}_id`
|
const entityIdKey = `${actor_type}_id`
|
||||||
const entityId = authIdentity.app_metadata?.[entityIdKey]
|
const entityId = authIdentity?.app_metadata?.[entityIdKey] as
|
||||||
|
| string
|
||||||
|
| undefined
|
||||||
if (success) {
|
if (success) {
|
||||||
const { http } = req.scope.resolve(
|
const { http } = req.scope.resolve(
|
||||||
ContainerRegistrationKeys.CONFIG_MODULE
|
ContainerRegistrationKeys.CONFIG_MODULE
|
||||||
@@ -54,9 +56,9 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
const { jwtSecret, jwtExpiresIn } = http
|
const { jwtSecret, jwtExpiresIn } = http
|
||||||
const token = generateJwtToken(
|
const token = generateJwtToken(
|
||||||
{
|
{
|
||||||
actor_id: entityId,
|
actor_id: entityId ?? "",
|
||||||
actor_type,
|
actor_type,
|
||||||
auth_identity_id: authIdentity.id,
|
auth_identity_id: authIdentity?.id ?? "",
|
||||||
app_metadata: {
|
app_metadata: {
|
||||||
[entityIdKey]: entityId,
|
[entityIdKey]: entityId,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,14 +57,16 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
).projectConfig
|
).projectConfig
|
||||||
|
|
||||||
const entityIdKey = `${actor_type}_id`
|
const entityIdKey = `${actor_type}_id`
|
||||||
const entityId = authIdentity.app_metadata?.[entityIdKey]
|
const entityId = authIdentity?.app_metadata?.[entityIdKey] as
|
||||||
|
| string
|
||||||
|
| undefined
|
||||||
const { jwtSecret, jwtExpiresIn } = http
|
const { jwtSecret, jwtExpiresIn } = http
|
||||||
|
|
||||||
const token = generateJwtToken(
|
const token = generateJwtToken(
|
||||||
{
|
{
|
||||||
actor_id: entityId,
|
actor_id: entityId ?? "",
|
||||||
actor_type,
|
actor_type,
|
||||||
auth_identity_id: authIdentity.id,
|
auth_identity_id: authIdentity?.id ?? "",
|
||||||
app_metadata: {
|
app_metadata: {
|
||||||
[entityIdKey]: entityId,
|
[entityIdKey]: entityId,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ export default async function ({
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We know the authIdentity is not undefined
|
||||||
await authService.update({
|
await authService.update({
|
||||||
id: authIdentity.id,
|
id: authIdentity!.id,
|
||||||
app_metadata: {
|
app_metadata: {
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
|
import { IAuthModuleService } from "@medusajs/types"
|
||||||
import { AuthIdentity } from "@models"
|
import { AuthIdentity } from "@models"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
|
||||||
|
|
||||||
export async function createAuthIdentities(
|
export async function createAuthIdentities(
|
||||||
manager: SqlEntityManager,
|
service: IAuthModuleService,
|
||||||
userData: any[] = [
|
userData: any[] = [
|
||||||
{
|
{
|
||||||
id: "test-id",
|
id: "test-id",
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test-id",
|
entity_id: "test-id",
|
||||||
provider: "manual",
|
provider: "manual",
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "test-id-1",
|
id: "test-id-1",
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test-id-1",
|
entity_id: "test-id-1",
|
||||||
provider: "manual",
|
provider: "manual",
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider_identities: [
|
||||||
{
|
{
|
||||||
entity_id: "test-id-2",
|
entity_id: "test-id-2",
|
||||||
provider: "store",
|
provider: "store",
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
): Promise<AuthIdentity[]> {
|
): Promise<AuthIdentity[]> {
|
||||||
const authIdentities: AuthIdentity[] = []
|
return await service.create(userData)
|
||||||
|
|
||||||
for (const user of userData) {
|
|
||||||
const authIdentity = manager.create(AuthIdentity, user)
|
|
||||||
|
|
||||||
authIdentities.push(authIdentity)
|
|
||||||
}
|
|
||||||
|
|
||||||
await manager.persistAndFlush(authIdentities)
|
|
||||||
|
|
||||||
return authIdentities
|
|
||||||
}
|
}
|
||||||
@@ -23,10 +23,14 @@ export class AuthServiceFixtures extends AbstractAuthModuleProvider {
|
|||||||
try {
|
try {
|
||||||
authIdentity = await service.retrieve({
|
authIdentity = await service.retrieve({
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
provider: this.provider,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (authIdentity.provider_metadata?.password === password) {
|
// The provider has to be present, guaranteed by the retrieve filter above.
|
||||||
|
const providerIdentity = authIdentity.provider_identities?.find(
|
||||||
|
(pi) => pi.provider === this.provider
|
||||||
|
)!
|
||||||
|
|
||||||
|
if (providerIdentity.provider_metadata?.password === password) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
authIdentity,
|
authIdentity,
|
||||||
@@ -36,7 +40,6 @@ export class AuthServiceFixtures extends AbstractAuthModuleProvider {
|
|||||||
if (error.type === MedusaError.Types.NOT_FOUND) {
|
if (error.type === MedusaError.Types.NOT_FOUND) {
|
||||||
const createdAuthIdentity = await service.create({
|
const createdAuthIdentity = await service.create({
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
provider: this.provider,
|
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password,
|
password,
|
||||||
},
|
},
|
||||||
|
|||||||
+41
-13
@@ -13,22 +13,31 @@ moduleIntegrationTestRunner({
|
|||||||
}: SuiteOptions<IAuthModuleService>) => {
|
}: SuiteOptions<IAuthModuleService>) => {
|
||||||
describe("AuthModuleService - AuthIdentity", () => {
|
describe("AuthModuleService - AuthIdentity", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createAuthIdentities(MikroOrmWrapper.forkManager())
|
await createAuthIdentities(service)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("listAuthIdentities", () => {
|
describe("listAuthIdentities", () => {
|
||||||
it("should list authIdentities", async () => {
|
it("should list authIdentities", async () => {
|
||||||
const authIdentities = await service.list()
|
const authIdentities = await service.list(
|
||||||
|
{},
|
||||||
|
{ relations: ["provider_identities"] }
|
||||||
|
)
|
||||||
|
|
||||||
expect(authIdentities).toEqual([
|
expect(authIdentities).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: "store",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ provider: "store" }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: "manual",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ provider: "manual" }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: "manual",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ provider: "manual" }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@@ -47,7 +56,9 @@ moduleIntegrationTestRunner({
|
|||||||
|
|
||||||
it("should list authIdentities by provider", async () => {
|
it("should list authIdentities by provider", async () => {
|
||||||
const authIdentities = await service.list({
|
const authIdentities = await service.list({
|
||||||
|
provider_identities: {
|
||||||
provider: "manual",
|
provider: "manual",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(authIdentities).toEqual([
|
expect(authIdentities).toEqual([
|
||||||
@@ -63,25 +74,34 @@ moduleIntegrationTestRunner({
|
|||||||
|
|
||||||
describe("listAndCountAuthIdentities", () => {
|
describe("listAndCountAuthIdentities", () => {
|
||||||
it("should list and count authIdentities", async () => {
|
it("should list and count authIdentities", async () => {
|
||||||
const [authIdentities, count] = await service.listAndCount()
|
const [authIdentities, count] = await service.listAndCount(
|
||||||
|
{},
|
||||||
|
{ relations: ["provider_identities"] }
|
||||||
|
)
|
||||||
|
|
||||||
expect(count).toEqual(3)
|
expect(count).toEqual(3)
|
||||||
expect(authIdentities).toEqual([
|
expect(authIdentities).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: "store",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ provider: "store" }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: "manual",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ provider: "manual" }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: "manual",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ provider: "manual" }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should listAndCount authIdentities by provider_id", async () => {
|
it("should listAndCount authIdentities by provider_id", async () => {
|
||||||
const [authIdentities, count] = await service.listAndCount({
|
const [authIdentities, count] = await service.listAndCount({
|
||||||
provider: "manual",
|
provider_identities: { provider: "manual" },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(count).toEqual(2)
|
expect(count).toEqual(2)
|
||||||
@@ -131,7 +151,11 @@ moduleIntegrationTestRunner({
|
|||||||
id: "test-id-1",
|
id: "test-id-1",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
expect(authIdentity["password_hash"]).toEqual(undefined)
|
expect(
|
||||||
|
authIdentity.provider_identities?.[0].provider_metadata?.[
|
||||||
|
"password_hash"
|
||||||
|
]
|
||||||
|
).toEqual(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should throw an error when a authIdentityId is not provided", async () => {
|
it("should throw an error when a authIdentityId is not provided", async () => {
|
||||||
@@ -196,14 +220,14 @@ moduleIntegrationTestRunner({
|
|||||||
await service.update([
|
await service.update([
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
provider_metadata: { email: "test@email.com" },
|
app_metadata: { email: "test@email.com" },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const [authIdentity] = await service.list({ id: [id] })
|
const [authIdentity] = await service.list({ id: [id] })
|
||||||
expect(authIdentity).toEqual(
|
expect(authIdentity).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider_metadata: { email: "test@email.com" },
|
app_metadata: { email: "test@email.com" },
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -214,9 +238,13 @@ moduleIntegrationTestRunner({
|
|||||||
await service.create([
|
await service.create([
|
||||||
{
|
{
|
||||||
id: "test",
|
id: "test",
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
provider: "manual",
|
provider: "manual",
|
||||||
entity_id: "test",
|
entity_id: "test",
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const [authIdentity, count] = await service.listAndCount({
|
const [authIdentity, count] = await service.listAndCount({
|
||||||
|
|||||||
+14
-3
@@ -28,11 +28,15 @@ moduleIntegrationTestRunner({
|
|||||||
describe("Auth Module Service", () => {
|
describe("Auth Module Service", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await service.create({
|
await service.create({
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "plaintextpass",
|
provider: "plaintextpass",
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: "plaintext",
|
password: "plaintext",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,7 +69,9 @@ moduleIntegrationTestRunner({
|
|||||||
success: true,
|
success: true,
|
||||||
authIdentity: expect.objectContaining({
|
authIdentity: expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
entity_id: "test@admin.com",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ entity_id: "test@admin.com" }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -97,12 +103,17 @@ moduleIntegrationTestRunner({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const dbAuthIdentity = await service.retrieve(result.authIdentity.id)
|
const dbAuthIdentity = await service.retrieve(
|
||||||
|
result.authIdentity?.id!,
|
||||||
|
{ relations: ["provider_identities"] }
|
||||||
|
)
|
||||||
|
|
||||||
expect(dbAuthIdentity).toEqual(
|
expect(dbAuthIdentity).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
entity_id: "new@admin.com",
|
provider_identities: [
|
||||||
|
expect.objectContaining({ entity_id: "new@admin.com" }),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,68 @@
|
|||||||
{
|
{
|
||||||
"namespaces": ["public"],
|
"namespaces": [
|
||||||
|
"public"
|
||||||
|
],
|
||||||
"name": "public",
|
"name": "public",
|
||||||
"tables": [
|
"tables": [
|
||||||
|
{
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
|
"app_metadata": {
|
||||||
|
"name": "app_metadata",
|
||||||
|
"type": "jsonb",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "json"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamptz",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"length": 6,
|
||||||
|
"default": "now()",
|
||||||
|
"mappedType": "datetime"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamptz",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"length": 6,
|
||||||
|
"default": "now()",
|
||||||
|
"mappedType": "datetime"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "auth_identity",
|
||||||
|
"schema": "public",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"keyName": "auth_identity_pkey",
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"composite": false,
|
||||||
|
"primary": true,
|
||||||
|
"unique": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"checks": [],
|
||||||
|
"foreignKeys": {}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -31,6 +92,15 @@
|
|||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
|
"auth_identity_id": {
|
||||||
|
"name": "auth_identity_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"user_metadata": {
|
"user_metadata": {
|
||||||
"name": "user_metadata",
|
"name": "user_metadata",
|
||||||
"type": "jsonb",
|
"type": "jsonb",
|
||||||
@@ -40,15 +110,6 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "json"
|
"mappedType": "json"
|
||||||
},
|
},
|
||||||
"app_metadata": {
|
|
||||||
"name": "app_metadata",
|
|
||||||
"type": "jsonb",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": false,
|
|
||||||
"mappedType": "json"
|
|
||||||
},
|
|
||||||
"provider_metadata": {
|
"provider_metadata": {
|
||||||
"name": "provider_metadata",
|
"name": "provider_metadata",
|
||||||
"type": "jsonb",
|
"type": "jsonb",
|
||||||
@@ -57,28 +118,75 @@
|
|||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "json"
|
"mappedType": "json"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamptz",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"length": 6,
|
||||||
|
"default": "now()",
|
||||||
|
"mappedType": "datetime"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamptz",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"length": 6,
|
||||||
|
"default": "now()",
|
||||||
|
"mappedType": "datetime"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "auth_identity",
|
"name": "provider_identity",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"keyName": "IDX_auth_identity_provider_entity_id",
|
"keyName": "IDX_provider_identity_auth_identity_id",
|
||||||
"columnNames": ["provider", "entity_id"],
|
"columnNames": [],
|
||||||
"composite": true,
|
"composite": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": true
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_provider_identity_auth_identity_id\" ON \"provider_identity\" (auth_identity_id)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "auth_identity_pkey",
|
"keyName": "IDX_provider_identity_provider_entity_id",
|
||||||
"columnNames": ["id"],
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_provider_identity_provider_entity_id\" ON \"provider_identity\" (entity_id, provider)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "provider_identity_pkey",
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"primary": true,
|
"primary": true,
|
||||||
"unique": true
|
"unique": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"checks": [],
|
"checks": [],
|
||||||
"foreignKeys": {}
|
"foreignKeys": {
|
||||||
|
"provider_identity_auth_identity_id_foreign": {
|
||||||
|
"constraintName": "provider_identity_auth_identity_id_foreign",
|
||||||
|
"columnNames": [
|
||||||
|
"auth_identity_id"
|
||||||
|
],
|
||||||
|
"localTableName": "public.provider_identity",
|
||||||
|
"referencedColumnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"referencedTableName": "public.auth_identity",
|
||||||
|
"deleteRule": "cascade",
|
||||||
|
"updateRule": "cascade"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,9 @@ export class Migration20240205025928 extends Migration {
|
|||||||
this.addSql(
|
this.addSql(
|
||||||
'create table if not exists "auth_identity" ("id" text not null, "entity_id" text not null, "provider" text not null, "user_metadata" jsonb null, "app_metadata" jsonb null, "provider_metadata" jsonb null, constraint "auth_identity_pkey" primary key ("id"));'
|
'create table if not exists "auth_identity" ("id" text not null, "entity_id" text not null, "provider" text not null, "user_metadata" jsonb null, "app_metadata" jsonb null, "provider_metadata" jsonb null, constraint "auth_identity_pkey" primary key ("id"));'
|
||||||
)
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table "auth_identity" drop constraint if exists "IDX_auth_identity_provider_entity_id"'
|
||||||
|
)
|
||||||
this.addSql(
|
this.addSql(
|
||||||
'alter table "auth_identity" add constraint "IDX_auth_identity_provider_entity_id" unique ("provider", "entity_id");'
|
'alter table "auth_identity" add constraint "IDX_auth_identity_provider_entity_id" unique ("provider", "entity_id");'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20240529080336 extends Migration {
|
||||||
|
|
||||||
|
async up(): Promise<void> {
|
||||||
|
this.addSql('create table if not exists "provider_identity" ("id" text not null, "entity_id" text not null, "provider" text not null, "auth_identity_id" text not null, "user_metadata" jsonb null, "provider_metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), constraint "provider_identity_pkey" primary key ("id"));');
|
||||||
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_provider_identity_auth_identity_id" ON "provider_identity" (auth_identity_id);');
|
||||||
|
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_provider_identity_provider_entity_id" ON "provider_identity" (entity_id, provider);');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "provider_identity" add constraint "provider_identity_auth_identity_id_foreign" foreign key ("auth_identity_id") references "auth_identity" ("id") on update cascade on delete cascade;');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "auth_identity" add column if not exists "created_at" timestamptz not null default now(), add column "updated_at" timestamptz not null default now();');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "auth_identity" drop constraint if exists "IDX_auth_identity_provider_entity_id";');
|
||||||
|
this.addSql('alter table if exists "auth_identity" drop column if exists "entity_id";');
|
||||||
|
this.addSql('alter table if exists "auth_identity" drop column if exists "provider";');
|
||||||
|
this.addSql('alter table if exists "auth_identity" drop column if exists "user_metadata";');
|
||||||
|
this.addSql('alter table if exists "auth_identity" drop column if exists "provider_metadata";');
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(): Promise<void> {
|
||||||
|
this.addSql('drop table if exists "provider_identity" cascade;');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "auth_identity" add column if not exists "entity_id" text not null, add column "provider" text not null, add column "user_metadata" jsonb null, add column "provider_metadata" jsonb null;');
|
||||||
|
this.addSql('alter table if exists "auth_identity" alter column if exists "app_metadata" type jsonb using ("app_metadata"::jsonb);');
|
||||||
|
this.addSql('alter table if exists "auth_identity" alter column if exists "app_metadata" set not null;');
|
||||||
|
this.addSql('alter table if exists "auth_identity" add constraint "IDX_auth_identity_provider_entity_id" unique ("provider", "entity_id");');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,50 +1,45 @@
|
|||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
|
Collection,
|
||||||
Entity,
|
Entity,
|
||||||
OnInit,
|
OnInit,
|
||||||
OptionalProps,
|
OneToMany,
|
||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
Property,
|
Property,
|
||||||
Unique,
|
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
|
|
||||||
import { generateEntityId } from "@medusajs/utils"
|
import { generateEntityId } from "@medusajs/utils"
|
||||||
|
import ProviderIdentity from "./provider-identity"
|
||||||
type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata"
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Unique({
|
|
||||||
properties: ["provider", "entity_id"],
|
|
||||||
name: "IDX_auth_identity_provider_entity_id",
|
|
||||||
})
|
|
||||||
export default class AuthIdentity {
|
export default class AuthIdentity {
|
||||||
[OptionalProps]: OptionalFields
|
|
||||||
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
@PrimaryKey({ columnType: "text" })
|
||||||
id!: string
|
id!: string
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
@OneToMany(() => ProviderIdentity, (o) => o.auth_identity)
|
||||||
entity_id: string
|
provider_identities = new Collection<ProviderIdentity>(this)
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
provider: string
|
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
|
||||||
user_metadata: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
app_metadata: Record<string, unknown> | null
|
app_metadata: Record<string, unknown> | null
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
@Property({
|
||||||
provider_metadata: Record<string, unknown> | null = null
|
onCreate: () => new Date(),
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
})
|
||||||
|
created_at: Date
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
onUpdate: () => new Date(),
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
})
|
||||||
|
updated_at: Date
|
||||||
|
|
||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
|
@OnInit()
|
||||||
onCreate() {
|
onCreate() {
|
||||||
this.id = generateEntityId(this.id, "authid")
|
this.id = generateEntityId(this.id, "authid")
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "authid")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { default as AuthIdentity } from "./auth-identity"
|
export { default as AuthIdentity } from "./auth-identity"
|
||||||
|
export { default as ProviderIdentity } from "./provider-identity"
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
BeforeCreate,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
OnInit,
|
||||||
|
PrimaryKey,
|
||||||
|
Property,
|
||||||
|
} from "@mikro-orm/core"
|
||||||
|
|
||||||
|
import {
|
||||||
|
createPsqlIndexStatementHelper,
|
||||||
|
generateEntityId,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import AuthIdentity from "./auth-identity"
|
||||||
|
|
||||||
|
const providerEntityIdIndexName = "IDX_provider_identity_provider_entity_id"
|
||||||
|
const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({
|
||||||
|
name: providerEntityIdIndexName,
|
||||||
|
tableName: "provider_identity",
|
||||||
|
columns: ["entity_id", "provider"],
|
||||||
|
unique: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const authIdentityIndexName = "IDX_provider_identity_auth_identity_id"
|
||||||
|
const authIdentityIndexStatement = createPsqlIndexStatementHelper({
|
||||||
|
name: authIdentityIndexName,
|
||||||
|
tableName: "provider_identity",
|
||||||
|
columns: ["auth_identity_id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@providerEntityIdIndexStatement.MikroORMIndex()
|
||||||
|
@authIdentityIndexStatement.MikroORMIndex()
|
||||||
|
export default class ProviderIdentity {
|
||||||
|
@PrimaryKey({ columnType: "text" })
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Property({ columnType: "text" })
|
||||||
|
entity_id: string
|
||||||
|
|
||||||
|
@Property({ columnType: "text" })
|
||||||
|
provider: string
|
||||||
|
|
||||||
|
@ManyToOne(() => AuthIdentity, {
|
||||||
|
columnType: "text",
|
||||||
|
fieldName: "auth_identity_id",
|
||||||
|
mapToPk: true,
|
||||||
|
onDelete: "cascade",
|
||||||
|
})
|
||||||
|
auth_identity_id: string
|
||||||
|
|
||||||
|
@ManyToOne(() => AuthIdentity, {
|
||||||
|
persist: false,
|
||||||
|
})
|
||||||
|
auth_identity: AuthIdentity
|
||||||
|
|
||||||
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
|
user_metadata: Record<string, unknown> | null
|
||||||
|
|
||||||
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
|
provider_metadata: Record<string, unknown> | null = null
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
})
|
||||||
|
created_at: Date
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
onUpdate: () => new Date(),
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
})
|
||||||
|
updated_at: Date
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
@OnInit()
|
||||||
|
onCreate() {
|
||||||
|
this.id = generateEntityId(this.id, "provid")
|
||||||
|
this.auth_identity_id ??= this.auth_identity?.id ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
ModulesSdkTypes,
|
ModulesSdkTypes,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
import { AuthIdentity } from "@models"
|
import { AuthIdentity, ProviderIdentity } from "@models"
|
||||||
|
|
||||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||||
|
|
||||||
@@ -21,34 +21,40 @@ import {
|
|||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import AuthProviderService from "./auth-provider"
|
import AuthProviderService from "./auth-provider"
|
||||||
|
import { populate } from "dotenv"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
authIdentityService: ModulesSdkTypes.InternalModuleService<any>
|
authIdentityService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
|
providerIdentityService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
authProviderService: AuthProviderService
|
authProviderService: AuthProviderService
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateMethodForModels = [AuthIdentity]
|
const generateMethodForModels = [AuthIdentity, ProviderIdentity]
|
||||||
|
|
||||||
export default class AuthModuleService<
|
export default class AuthModuleService<
|
||||||
TAuthIdentity extends AuthIdentity = AuthIdentity
|
TAuthIdentity extends AuthIdentity = AuthIdentity,
|
||||||
|
TProviderIdentity extends ProviderIdentity = ProviderIdentity
|
||||||
>
|
>
|
||||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||||
InjectedDependencies,
|
InjectedDependencies,
|
||||||
AuthTypes.AuthIdentityDTO,
|
AuthTypes.AuthIdentityDTO,
|
||||||
{
|
{
|
||||||
AuthIdentity: { dto: AuthTypes.AuthIdentityDTO }
|
AuthIdentity: { dto: AuthTypes.AuthIdentityDTO }
|
||||||
|
ProviderIdentity: { dto: AuthTypes.ProviderIdentityDTO }
|
||||||
}
|
}
|
||||||
>(AuthIdentity, generateMethodForModels, entityNameToLinkableKeysMap)
|
>(AuthIdentity, generateMethodForModels, entityNameToLinkableKeysMap)
|
||||||
implements AuthTypes.IAuthModuleService
|
implements AuthTypes.IAuthModuleService
|
||||||
{
|
{
|
||||||
protected baseRepository_: DAL.RepositoryService
|
protected baseRepository_: DAL.RepositoryService
|
||||||
protected authIdentityService_: ModulesSdkTypes.InternalModuleService<TAuthIdentity>
|
protected authIdentityService_: ModulesSdkTypes.InternalModuleService<TAuthIdentity>
|
||||||
|
protected providerIdentityService_: ModulesSdkTypes.InternalModuleService<TProviderIdentity>
|
||||||
protected readonly authProviderService_: AuthProviderService
|
protected readonly authProviderService_: AuthProviderService
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
authIdentityService,
|
authIdentityService,
|
||||||
|
providerIdentityService,
|
||||||
authProviderService,
|
authProviderService,
|
||||||
baseRepository,
|
baseRepository,
|
||||||
}: InjectedDependencies,
|
}: InjectedDependencies,
|
||||||
@@ -60,6 +66,7 @@ export default class AuthModuleService<
|
|||||||
this.baseRepository_ = baseRepository
|
this.baseRepository_ = baseRepository
|
||||||
this.authIdentityService_ = authIdentityService
|
this.authIdentityService_ = authIdentityService
|
||||||
this.authProviderService_ = authProviderService
|
this.authProviderService_ = authProviderService
|
||||||
|
this.providerIdentityService_ = providerIdentityService
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig(): ModuleJoinerConfig {
|
__joinerConfig(): ModuleJoinerConfig {
|
||||||
@@ -132,7 +139,7 @@ export default class AuthModuleService<
|
|||||||
return await this.authProviderService_.authenticate(
|
return await this.authProviderService_.authenticate(
|
||||||
provider,
|
provider,
|
||||||
authenticationData,
|
authenticationData,
|
||||||
this.getAuthIdentityProviderService()
|
this.getAuthIdentityProviderService(provider)
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
@@ -147,20 +154,29 @@ export default class AuthModuleService<
|
|||||||
return await this.authProviderService_.validateCallback(
|
return await this.authProviderService_.validateCallback(
|
||||||
provider,
|
provider,
|
||||||
authenticationData,
|
authenticationData,
|
||||||
this.getAuthIdentityProviderService()
|
this.getAuthIdentityProviderService(provider)
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthIdentityProviderService(): AuthIdentityProviderService {
|
getAuthIdentityProviderService(
|
||||||
|
provider: string
|
||||||
|
): AuthIdentityProviderService {
|
||||||
return {
|
return {
|
||||||
retrieve: async ({ entity_id, provider }) => {
|
retrieve: async ({ entity_id }) => {
|
||||||
const authIdentities = await this.authIdentityService_.list({
|
const authIdentities = await this.authIdentityService_.list(
|
||||||
|
{
|
||||||
|
provider_identities: {
|
||||||
entity_id,
|
entity_id,
|
||||||
provider,
|
provider,
|
||||||
})
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relations: ["provider_identities"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (!authIdentities.length) {
|
if (!authIdentities.length) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -180,8 +196,26 @@ export default class AuthModuleService<
|
|||||||
authIdentities[0]
|
authIdentities[0]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
create: async (data: AuthTypes.CreateAuthIdentityDTO) => {
|
|
||||||
const createdAuthIdentity = await this.authIdentityService_.create(data)
|
create: async (data: {
|
||||||
|
entity_id: string
|
||||||
|
provider_metadata?: Record<string, unknown>
|
||||||
|
user_metadata?: Record<string, unknown>
|
||||||
|
}) => {
|
||||||
|
const normalizedRequest = {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
|
entity_id: data.entity_id,
|
||||||
|
provider_metadata: data.provider_metadata,
|
||||||
|
user_metadata: data.user_metadata,
|
||||||
|
provider,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAuthIdentity = await this.authIdentityService_.create(
|
||||||
|
normalizedRequest
|
||||||
|
)
|
||||||
|
|
||||||
return await this.baseRepository_.serialize<AuthTypes.AuthIdentityDTO>(
|
return await this.baseRepository_.serialize<AuthTypes.AuthIdentityDTO>(
|
||||||
createdAuthIdentity
|
createdAuthIdentity
|
||||||
|
|||||||
+21
-5
@@ -22,7 +22,7 @@ describe("Email password auth provider", () => {
|
|||||||
it("return error if email is not passed", async () => {
|
it("return error if email is not passed", async () => {
|
||||||
const resp = await emailpassService.authenticate(
|
const resp = await emailpassService.authenticate(
|
||||||
{ body: { password: "otherpass" } },
|
{ body: { password: "otherpass" } },
|
||||||
{}
|
{} as any
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(resp).toEqual({
|
expect(resp).toEqual({
|
||||||
@@ -34,7 +34,7 @@ describe("Email password auth provider", () => {
|
|||||||
it("return error if password is not passed", async () => {
|
it("return error if password is not passed", async () => {
|
||||||
const resp = await emailpassService.authenticate(
|
const resp = await emailpassService.authenticate(
|
||||||
{ body: { email: "test@admin.com" } },
|
{ body: { email: "test@admin.com" } },
|
||||||
{}
|
{} as any
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(resp).toEqual({
|
expect(resp).toEqual({
|
||||||
@@ -50,18 +50,22 @@ describe("Email password auth provider", () => {
|
|||||||
const authServiceSpies = {
|
const authServiceSpies = {
|
||||||
retrieve: jest.fn().mockImplementation(() => {
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "emailpass",
|
provider: "emailpass",
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: passwordHash.toString("base64"),
|
password: passwordHash.toString("base64"),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await emailpassService.authenticate(
|
const resp = await emailpassService.authenticate(
|
||||||
{ body: { email: "test@admin.com", password: "otherpass" } },
|
{ body: { email: "test@admin.com", password: "otherpass" } },
|
||||||
authServiceSpies
|
authServiceSpies as any
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
||||||
@@ -78,18 +82,22 @@ describe("Email password auth provider", () => {
|
|||||||
const authServiceSpies = {
|
const authServiceSpies = {
|
||||||
retrieve: jest.fn().mockImplementation(() => {
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "emailpass",
|
provider: "emailpass",
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: passwordHash.toString("base64"),
|
password: passwordHash.toString("base64"),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await emailpassService.authenticate(
|
const resp = await emailpassService.authenticate(
|
||||||
{ body: { email: "test@admin.com", password: "somepass" } },
|
{ body: { email: "test@admin.com", password: "somepass" } },
|
||||||
authServiceSpies
|
authServiceSpies as any
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
||||||
@@ -97,9 +105,13 @@ describe("Email password auth provider", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
success: true,
|
success: true,
|
||||||
authIdentity: expect.objectContaining({
|
authIdentity: expect.objectContaining({
|
||||||
|
provider_identities: [
|
||||||
|
expect.objectContaining({
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider_metadata: {},
|
provider_metadata: {},
|
||||||
}),
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -111,11 +123,15 @@ describe("Email password auth provider", () => {
|
|||||||
}),
|
}),
|
||||||
create: jest.fn().mockImplementation(() => {
|
create: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "emailpass",
|
provider: "emailpass",
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: "somehash",
|
password: "somehash",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -128,7 +144,7 @@ describe("Email password auth provider", () => {
|
|||||||
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
expect(authServiceSpies.retrieve).toHaveBeenCalled()
|
||||||
expect(authServiceSpies.create).toHaveBeenCalled()
|
expect(authServiceSpies.create).toHaveBeenCalled()
|
||||||
|
|
||||||
expect(resp.authIdentity).toEqual(
|
expect(resp.authIdentity?.provider_identities?.[0]).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider_metadata: {},
|
provider_metadata: {},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
AuthenticationResponse,
|
AuthenticationResponse,
|
||||||
AuthenticationInput,
|
AuthenticationInput,
|
||||||
AuthIdentityProviderService,
|
AuthIdentityProviderService,
|
||||||
|
AuthIdentityDTO,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
AbstractAuthModuleProvider,
|
AbstractAuthModuleProvider,
|
||||||
@@ -53,12 +54,11 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
error: "Email should be a string",
|
error: "Email should be a string",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let authIdentity
|
let authIdentity: AuthIdentityDTO | undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
authIdentity = await authIdentityService.retrieve({
|
authIdentity = await authIdentityService.retrieve({
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
provider: this.provider,
|
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.type === MedusaError.Types.NOT_FOUND) {
|
if (error.type === MedusaError.Types.NOT_FOUND) {
|
||||||
@@ -67,14 +67,16 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
|
|
||||||
const createdAuthIdentity = await authIdentityService.create({
|
const createdAuthIdentity = await authIdentityService.create({
|
||||||
entity_id: email,
|
entity_id: email,
|
||||||
provider: this.provider,
|
|
||||||
provider_metadata: {
|
provider_metadata: {
|
||||||
password: passwordHash.toString("base64"),
|
password: passwordHash.toString("base64"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const copy = JSON.parse(JSON.stringify(createdAuthIdentity))
|
const copy = JSON.parse(JSON.stringify(createdAuthIdentity))
|
||||||
delete copy.provider_metadata?.password
|
const providerIdentity = copy.provider_identities?.find(
|
||||||
|
(pi) => pi.provider === this.provider
|
||||||
|
)!
|
||||||
|
delete providerIdentity.provider_metadata?.password
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -85,7 +87,10 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordHash = authIdentity.provider_metadata?.password
|
const providerIdentity = authIdentity.provider_identities?.find(
|
||||||
|
(pi) => pi.provider === this.provider
|
||||||
|
)!
|
||||||
|
const passwordHash = providerIdentity.provider_metadata?.password
|
||||||
|
|
||||||
if (isString(passwordHash)) {
|
if (isString(passwordHash)) {
|
||||||
const buf = Buffer.from(passwordHash as string, "base64")
|
const buf = Buffer.from(passwordHash as string, "base64")
|
||||||
@@ -93,7 +98,10 @@ export class EmailPassAuthService extends AbstractAuthModuleProvider {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
const copy = JSON.parse(JSON.stringify(authIdentity))
|
const copy = JSON.parse(JSON.stringify(authIdentity))
|
||||||
delete copy.provider_metadata!.password
|
const providerIdentity = copy.provider_identities?.find(
|
||||||
|
(pi) => pi.provider === this.provider
|
||||||
|
)!
|
||||||
|
delete providerIdentity.provider_metadata?.password
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success,
|
success,
|
||||||
|
|||||||
@@ -158,8 +158,12 @@ describe("Google auth provider", () => {
|
|||||||
}),
|
}),
|
||||||
create: jest.fn().mockImplementation(() => {
|
create: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "google",
|
provider: "google",
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -177,9 +181,13 @@ describe("Google auth provider", () => {
|
|||||||
success: true,
|
success: true,
|
||||||
successRedirectUrl: baseUrl,
|
successRedirectUrl: baseUrl,
|
||||||
authIdentity: {
|
authIdentity: {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "google",
|
provider: "google",
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -187,8 +195,12 @@ describe("Google auth provider", () => {
|
|||||||
const authServiceSpies = {
|
const authServiceSpies = {
|
||||||
retrieve: jest.fn().mockImplementation(() => {
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "google",
|
provider: "google",
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
create: jest.fn().mockImplementation(() => {
|
create: jest.fn().mockImplementation(() => {
|
||||||
@@ -209,9 +221,13 @@ describe("Google auth provider", () => {
|
|||||||
success: true,
|
success: true,
|
||||||
successRedirectUrl: baseUrl,
|
successRedirectUrl: baseUrl,
|
||||||
authIdentity: {
|
authIdentity: {
|
||||||
|
provider_identities: [
|
||||||
|
{
|
||||||
entity_id: "test@admin.com",
|
entity_id: "test@admin.com",
|
||||||
provider: "google",
|
provider: "google",
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -130,13 +130,11 @@ export class GoogleAuthService extends AbstractAuthModuleProvider {
|
|||||||
try {
|
try {
|
||||||
authIdentity = await authIdentityService.retrieve({
|
authIdentity = await authIdentityService.retrieve({
|
||||||
entity_id,
|
entity_id,
|
||||||
provider: this.provider,
|
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.type === MedusaError.Types.NOT_FOUND) {
|
if (error.type === MedusaError.Types.NOT_FOUND) {
|
||||||
const createdAuthIdentity = await authIdentityService.create({
|
const createdAuthIdentity = await authIdentityService.create({
|
||||||
entity_id,
|
entity_id,
|
||||||
provider: this.provider,
|
|
||||||
user_metadata: userMetadata,
|
user_metadata: userMetadata,
|
||||||
})
|
})
|
||||||
authIdentity = createdAuthIdentity
|
authIdentity = createdAuthIdentity
|
||||||
|
|||||||
Reference in new issue
Block a user