-
-
Notifications
You must be signed in to change notification settings - Fork 487
Commit
implement social verification
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { socialUserInfoGuard, type SocialUserInfo, type ToZodObject } from '@logto/connector-kit'; | ||
import { | ||
VerificationType, | ||
type JsonObject, | ||
type SocialAuthorizationUrlPayload, | ||
type User, | ||
} from '@logto/schemas'; | ||
import { generateStandardId } from '@logto/shared'; | ||
import { z } from 'zod'; | ||
|
||
import { type WithLogContext } from '#src/middleware/koa-audit-log.js'; | ||
import { | ||
createSocialAuthorizationUrl, | ||
verifySocialIdentity, | ||
} from '#src/routes/interaction/utils/social-verification.js'; | ||
import type Libraries from '#src/tenants/Libraries.js'; | ||
import type Queries from '#src/tenants/Queries.js'; | ||
import type TenantContext from '#src/tenants/TenantContext.js'; | ||
|
||
import { type Verification } from './verification.js'; | ||
|
||
/** The JSON data type for the SocialVerification record stored in the interaction storage */ | ||
export type SocialVerificationRecordData = { | ||
id: string; | ||
connectorId: string; | ||
type: VerificationType.Social; | ||
/** | ||
* The social identity returned by the connector. | ||
*/ | ||
socialUserInfo?: SocialUserInfo; | ||
/** | ||
* The userId of the user that has been verified by the social identity. | ||
*/ | ||
userId?: string; | ||
}; | ||
|
||
export const socialVerificationRecordDataGuard = z.object({ | ||
id: z.string(), | ||
connectorId: z.string(), | ||
type: z.literal(VerificationType.Social), | ||
socialUserInfo: socialUserInfoGuard.optional(), | ||
userId: z.string().optional(), | ||
}) satisfies ToZodObject<SocialVerificationRecordData>; | ||
|
||
export class SocialVerification implements Verification { | ||
/** | ||
* Factory method to create a new SocialVerification instance | ||
*/ | ||
static create(libraries: Libraries, queries: Queries, connectorId: string) { | ||
return new SocialVerification(libraries, queries, { | ||
id: generateStandardId(), | ||
connectorId, | ||
type: VerificationType.Social, | ||
}); | ||
} | ||
Check warning on line 55 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L50-L55
|
||
|
||
public readonly id: string; | ||
public readonly type = VerificationType.Social; | ||
public readonly connectorId: string; | ||
public socialUserInfo?: SocialUserInfo; | ||
public userId?: string; | ||
|
||
constructor( | ||
private readonly libraries: Libraries, | ||
private readonly queries: Queries, | ||
data: SocialVerificationRecordData | ||
) { | ||
const { id, connectorId, socialUserInfo, userId } = | ||
socialVerificationRecordDataGuard.parse(data); | ||
|
||
this.id = id; | ||
this.connectorId = connectorId; | ||
this.socialUserInfo = socialUserInfo; | ||
this.userId = userId; | ||
} | ||
Check warning on line 75 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L64-L75
|
||
|
||
/** | ||
* Returns true if the social identity has been verified | ||
*/ | ||
get isVerified() { | ||
return Boolean(this.socialUserInfo); | ||
} | ||
Check warning on line 82 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L81-L82
|
||
|
||
get verifiedUserId() { | ||
return this.userId; | ||
} | ||
Check warning on line 86 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L85-L86
|
||
|
||
/** | ||
* Create the authorization URL for the social connector. | ||
* Store the connector session result in the provider's interaction storage. | ||
* | ||
* @remarks | ||
* Refers to the {@link createSocialAuthorizationUrl} method in the interaction/utils/social-verification.ts file. | ||
* Currently, all the intermediate connector session results are stored in the provider's interactionDetails separately, | ||
* apart from the new verification record. | ||
* For compatibility reasons, we keep using the old {@link createSocialAuthorizationUrl} method here as a single source of truth. | ||
* Especially for the SAML connectors, | ||
* SAML ACS endpoint will find the connector session result by the jti and assign it to the interaction storage. | ||
* We will need to update the SAML ACS endpoint before move the logic to this new SocialVerification class. | ||
* | ||
* TODO: Consider store the connector session result in the verification record directly. | ||
* SAML ACS endpoint will find the verification record by the jti and assign the connector session result to the verification record. | ||
*/ | ||
async createAuthorizationUrl( | ||
ctx: WithLogContext, | ||
tenantContext: TenantContext, | ||
{ state, redirectUri }: SocialAuthorizationUrlPayload | ||
) { | ||
return createSocialAuthorizationUrl(ctx, tenantContext, { | ||
connectorId: this.connectorId, | ||
state, | ||
redirectUri, | ||
}); | ||
} | ||
Check warning on line 114 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L105-L114
|
||
|
||
/** | ||
* Verify the social identity and store the social identity in the verification record. | ||
* | ||
* - Store the social identity in the verification record. | ||
* - Find the user by the social identity and store the userId in the verification record if the user exists. | ||
* | ||
* @remarks | ||
* Refer to the {@link verifySocialIdentity} method in the interaction/utils/social-verification.ts file. | ||
* For compatibility reasons, we keep using the old {@link verifySocialIdentity} method here as a single source of truth. | ||
* See the above {@link createAuthorizationUrl} method for more details. | ||
* | ||
* TODO: check the log event | ||
*/ | ||
async verify(ctx: WithLogContext, tenantContext: TenantContext, connectorData: JsonObject) { | ||
const socialUserInfo = await verifySocialIdentity( | ||
{ connectorId: this.connectorId, connectorData }, | ||
ctx, | ||
tenantContext | ||
); | ||
|
||
this.socialUserInfo = socialUserInfo; | ||
|
||
const user = await this.findUserBySocialIdentity(); | ||
this.userId = user?.id; | ||
} | ||
Check warning on line 140 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L130-L140
|
||
|
||
async findUserBySocialIdentity(): Promise<User | undefined> { | ||
const { socials } = this.libraries; | ||
const { | ||
users: { findUserByIdentity }, | ||
} = this.queries; | ||
|
||
if (!this.socialUserInfo) { | ||
return; | ||
} | ||
|
||
const { | ||
metadata: { target }, | ||
} = await socials.getConnector(this.connectorId); | ||
|
||
const user = await findUserByIdentity(target, this.socialUserInfo.id); | ||
|
||
return user ?? undefined; | ||
} | ||
Check warning on line 159 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L143-L159
|
||
|
||
async findRelatedUserBySocialIdentity(): ReturnType<typeof socials.findSocialRelatedUser> { | ||
const { socials } = this.libraries; | ||
|
||
if (!this.socialUserInfo) { | ||
return null; | ||
} | ||
|
||
return socials.findSocialRelatedUser(this.socialUserInfo); | ||
} | ||
Check warning on line 169 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L162-L169
|
||
|
||
toJson(): SocialVerificationRecordData { | ||
return { | ||
id: this.id, | ||
connectorId: this.connectorId, | ||
type: this.type, | ||
socialUserInfo: this.socialUserInfo, | ||
}; | ||
} | ||
Check warning on line 178 in packages/core/src/routes/experience/classes/verifications/social-verification.ts Codecov / codecov/patchpackages/core/src/routes/experience/classes/verifications/social-verification.ts#L172-L178
|
||
} |