feat(auth): adopt @siax/auth-zitadel canonical claim normalizer #475

Merged
admin merged 1 commits from feat/auth-zitadel-20261104 into main 2026-11-04 01:23:52 +00:00
4 changed files with 44 additions and 10 deletions
+3 -2
View File
@@ -52,6 +52,8 @@
"@radix-ui/react-toggle-group": "1.1.19",
"@radix-ui/react-tooltip": "1.2.16",
"@siax/0pt-decision": "workspace:*",
"@siax/access": "workspace:*",
"@siax/auth-zitadel": "0.1.1",
"@siax/charts": "^0.5.0",
"@siax/collaboration": "0.3.0",
"@siax/config": "^0.2.1",
@@ -81,8 +83,7 @@
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"zod": "3.25.76",
"@siax/access": "workspace:*"
"zod": "3.25.76"
},
"devDependencies": {
"@eslint/js": "^9.15.0",
+15
View File
@@ -108,6 +108,9 @@ importers:
'@siax/access':
specifier: workspace:*
version: link:packages/siax-access
'@siax/auth-zitadel':
specifier: 0.1.1
version: 0.1.1
'@siax/charts':
specifier: ^0.5.0
version: 0.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -1549,6 +1552,12 @@ packages:
'@siax/analytical-context@0.5.1':
resolution: {integrity: sha512-bqc2NGPyzLVcu4VUgz1XYsyghSM4c3ck5gBVFSzeRYMhe2cj0nf5Fm5n/sFaOuhMaKdDnX/TIR2FO6f21lgufg==, tarball: https://git.siax.io/api/packages/sax3l/npm/%40siax%2Fanalytical-context/-/0.5.1/analytical-context-0.5.1.tgz}
'@siax/auth-core@0.1.0':
resolution: {integrity: sha512-oTfEO4S6pv3rQ6bSh44+fRWPiwGr9nYCf6uWSBF8g5PFUspW6nr4wKQZVAkLNwfC0Pu6o+CH8ltsuu+fY8gOQA==, tarball: https://git.siax.io/api/packages/sax3l/npm/%40siax%2Fauth-core/-/0.1.0/auth-core-0.1.0.tgz}
'@siax/auth-zitadel@0.1.1':
resolution: {integrity: sha512-SmoBswG5P7szZIaETqmo1NN5RhdXLq2RDR/1wObfxHM2yUEXNSZQJznZoAb8z3lm/PvBgQhmnHiOHCfidN1ZQg==, tarball: https://git.siax.io/api/packages/sax3l/npm/%40siax%2Fauth-zitadel/-/0.1.1/auth-zitadel-0.1.1.tgz}
'@siax/charts@0.11.0':
resolution: {integrity: sha512-IQnby+h6jVMaRAM7DcfUMuMbSUjd8B3+AGk9N4pXk2GDudMRMjt0bTtu75637Jc1Mf4iNonvgp8ytKlS7KdW4A==, tarball: https://git.siax.io/api/packages/sax3l/npm/%40siax%2Fcharts/-/0.11.0/charts-0.11.0.tgz}
peerDependencies:
@@ -4236,6 +4245,12 @@ snapshots:
'@siax/analytical-context@0.5.1': {}
'@siax/auth-core@0.1.0': {}
'@siax/auth-zitadel@0.1.1':
dependencies:
'@siax/auth-core': 0.1.0
'@siax/charts@0.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3)':
dependencies:
'@siax/analytical-context': 0.5.1
+25 -8
View File
@@ -50,6 +50,7 @@ import {
type JWTPayload,
type JWTVerifyGetKey,
} from 'jose';
import { normalizeZitadelClaims, ZitadelClaimError } from '@siax/auth-zitadel';
/** Default fleet Zitadel issuer (OIDC). Overridable via ZITADEL_ISSUER. */
export const DEFAULT_ZITADEL_ISSUER = 'https://id-customers.siax.io';
@@ -254,13 +255,29 @@ export async function verifyBearer(
throw new BearerVerificationError('token_verification_failed', err);
}
const sub = typeof payload.sub === 'string' ? payload.sub : undefined;
if (!sub || sub.length === 0) {
throw new BearerVerificationError('token_missing_sub');
try {
const identity = normalizeZitadelClaims(payload, {
source: 'jwt',
tenantClaim: opts.tenantClaim ?? tenantClaimOverride(),
});
return {
sub: identity.principal.subject,
tenantId: identity.tenant.tenantId,
claims: payload,
};
} catch (err) {
if (err instanceof ZitadelClaimError) {
if (/sub/i.test(err.reason)) {
throw new BearerVerificationError('token_missing_sub');
}
if (/tenant/i.test(err.reason)) {
throw new BearerVerificationError('token_missing_tenant_claim');
}
if (/aud/i.test(err.reason)) {
throw new BearerVerificationError('token_invalid_audience');
}
throw new BearerVerificationError('token_verification_failed', err);
}
throw err;
}
const tenantId = deriveTenantId(payload, opts.tenantClaim ?? tenantClaimOverride());
if (!tenantId) {
throw new BearerVerificationError('token_missing_tenant_claim');
}
return { sub, tenantId, claims: payload };
}
+1
View File
@@ -40,6 +40,7 @@ async function sign(claims: Record<string, unknown>, opts: SignOpts = {}): Promi
.setProtectedHeader({ alg: 'RS256', kid: KID })
.setIssuedAt()
.setIssuer(opts.issuer ?? ISSUER)
.setAudience('siax-test')
.setExpirationTime(opts.expirationTime ?? '2h')
.sign(privateKey);
}