/** * Resolve a provider identity to a local user id, creating the user and/or * the link row according to the configured matching policy. * * Throws {@link SocialSignInRefusedError} when the policy says no; every * other failure mode (db down, etc.) throws its own error untouched. */ export declare function resolveSocialSignIn(provider: string, identity: SocialIdentity, store?: SocialSignInStore): Promise; /** * The social sign-in policy: which local user a provider identity resolves to * (stacksjs/stacks#2276). * * `@stacksjs/socials` covers the OAuth exchange and stops at a normalized * provider user; `Auth.loginUsingId()` covers session issuance. This is the * piece between them — the find-or-create decision every app previously * hand-rolled, and the one they usually got wrong in the same way: linking an * unverified provider email onto an existing local account, which lets an * attacker register a victim's address at any provider that skips email * verification and inherit the local account. * * That takeover guard is therefore NOT configurable: a provider identity only * links to an existing user by email when the provider explicitly vouches for * the address (`emailVerified === true`). What IS configurable is what happens * when no link row exists — `config.auth.socials.matching`: * * - `'link'` (default): a verified-email match links to the existing user; * no match creates a new user; an unverified match refuses. * - `'create'`: never match by email — a first-time provider identity always * becomes a new user. No linking means no takeover surface at all, at the * cost of duplicate accounts for people who registered with a password * first. * - `'refuse'`: only identities linked beforehand (from a signed-in session) * may sign in. The strictest posture; social becomes a second factor for * existing accounts rather than an acquisition channel. */ /** What the policy needs from a provider profile — @stacksjs/socials' `SocialUser` satisfies this. */ export declare interface SocialIdentity { id: string name?: string | null nickname?: string | null email?: string | null emailVerified?: boolean | null avatar?: string | null } export declare interface SocialSignInResult { userId: number createdUser: boolean linked: boolean } /** * Storage the policy runs against. The default store reads and writes the * `social_accounts` and `users` tables; tests inject their own. */ export declare interface SocialSignInStore { findLink: (provider: string, providerUserId: string) => Promise<{ userId: number } | undefined> createLink: (link: { userId: number, provider: string, providerUserId: string, providerEmail: string | null }) => Promise findUserIdByEmail: (email: string) => Promise createUser: (attrs: { name: string, email: string }) => Promise } export type SocialMatchingPolicy = 'link' | 'create' | 'refuse'; export type SocialRefusalReason = | 'no-linked-account' | 'unverified-provider-email' | 'no-email-to-create-with' | 'email-already-registered'; /** A sign-in the policy declines. `reason` is safe to show a visitor. */ export declare class SocialSignInRefusedError extends Error { readonly reason: SocialRefusalReason; constructor(reason: SocialRefusalReason, message: string); }