Initial Commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import type {
|
||||
Claims,
|
||||
Principal,
|
||||
PrincipalId,
|
||||
Scope,
|
||||
} from "@suaveplan/api-types/principal";
|
||||
import { AUTH_BA_CODES, BetterAuthError } from "../error-codes/error-codes.js";
|
||||
import type {
|
||||
BetterAuthSessionRecord,
|
||||
BetterAuthSessionView,
|
||||
BetterAuthUserView,
|
||||
PrincipalMapping,
|
||||
} from "../options/options.types.js";
|
||||
|
||||
const SENSITIVE_USER_FIELDS = new Set([
|
||||
"password",
|
||||
"passwordHash",
|
||||
"salt",
|
||||
"twoFactorSecret",
|
||||
"magicLinkToken",
|
||||
"resetPasswordToken",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Default Better-Auth user/session → `Principal` mapping. Produces a
|
||||
* stable `Principal` carrying:
|
||||
*
|
||||
* - `id` ← `user.id` (branded as `PrincipalId`).
|
||||
* - `scopes` ← `user.roles` (branded as `Scope[]`); empty array when
|
||||
* no roles are attached. Plugins/middleware further upstream
|
||||
* enrich the scope set; the mapper itself does no role-to-scope
|
||||
* translation beyond identity.
|
||||
* - `tenantId` ← `session.activeOrganizationId` (when present), else
|
||||
* `user.tenantId` (when present), else `undefined`.
|
||||
* - `claims` ← `user` minus sensitive fields plus a `session_id` claim
|
||||
* carrying the Better-Auth session id (when present).
|
||||
*
|
||||
* Throws {@link BetterAuthError} with code
|
||||
* `AUTH_BA.PRINCIPAL_MAPPING_FAILED` when `user.id` is missing — without a
|
||||
* stable id the principal cannot be persisted across requests.
|
||||
*/
|
||||
export const mapBetterAuthUserToPrincipal: PrincipalMapping = (
|
||||
view: BetterAuthSessionView
|
||||
): Principal => {
|
||||
const { user, session } = view;
|
||||
if (typeof user.id !== "string" || user.id.length === 0) {
|
||||
throw new BetterAuthError(
|
||||
AUTH_BA_CODES.PRINCIPAL_MAPPING_FAILED,
|
||||
"Better-Auth user is missing a non-empty `id`",
|
||||
{ context: { received: { idType: typeof user.id } } }
|
||||
);
|
||||
}
|
||||
const id = user.id as unknown as PrincipalId;
|
||||
const scopes = pickScopes(user.roles);
|
||||
const tenantId = pickTenant(user, session);
|
||||
const claims = buildClaims(user, session);
|
||||
if (tenantId !== undefined) {
|
||||
return Object.freeze({ id, scopes, tenantId, claims }) as Principal;
|
||||
}
|
||||
return Object.freeze({ id, scopes, claims }) as Principal;
|
||||
};
|
||||
|
||||
function pickScopes(
|
||||
roles: readonly string[] | null | undefined
|
||||
): readonly Scope[] {
|
||||
if (!Array.isArray(roles)) return [];
|
||||
const out: Scope[] = [];
|
||||
for (const role of roles) {
|
||||
if (typeof role === "string" && role.length > 0) {
|
||||
out.push(role as unknown as Scope);
|
||||
}
|
||||
}
|
||||
return Object.freeze(out);
|
||||
}
|
||||
|
||||
function pickTenant(
|
||||
user: BetterAuthUserView,
|
||||
session: BetterAuthSessionRecord
|
||||
): import("@suaveplan/api-types/principal").Principal["tenantId"] | undefined {
|
||||
const fromSession = session.activeOrganizationId;
|
||||
if (typeof fromSession === "string" && fromSession.length > 0) {
|
||||
return fromSession as unknown as import("@suaveplan/api-types/principal").Principal["tenantId"];
|
||||
}
|
||||
const fromUser = user.tenantId;
|
||||
if (typeof fromUser === "string" && fromUser.length > 0) {
|
||||
return fromUser as unknown as import("@suaveplan/api-types/principal").Principal["tenantId"];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildClaims(
|
||||
user: BetterAuthUserView,
|
||||
session: BetterAuthSessionRecord
|
||||
): Claims {
|
||||
const claims: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(user)) {
|
||||
if (SENSITIVE_USER_FIELDS.has(key)) continue;
|
||||
if (value === undefined) continue;
|
||||
claims[key] = value;
|
||||
}
|
||||
if (typeof session.id === "string" && session.id.length > 0) {
|
||||
claims["session_id"] = session.id;
|
||||
}
|
||||
return Object.freeze(claims);
|
||||
}
|
||||
Reference in New Issue
Block a user