@hasna/tenants 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ /** The `ssl` field shape accepted by `pg.Pool` / `pg.Client`. */
2
+ export type PgSslConfig = boolean | {
3
+ rejectUnauthorized: boolean;
4
+ ca?: string;
5
+ };
6
+ export interface TlsResolveOptions {
7
+ /** Inline CA bundle (PEM). Wins over every other CA source. */
8
+ ca?: string;
9
+ /** Path to a CA bundle PEM file, e.g. the Amazon RDS global bundle. */
10
+ caCertPath?: string;
11
+ /** Environment used to discover PGSSLROOTCERT / NODE_EXTRA_CA_CERTS. */
12
+ env?: Record<string, string | undefined>;
13
+ }
14
+ export type SslMode = "disable" | "prefer" | "require" | "verify-ca" | "verify-full";
15
+ /**
16
+ * Extract the effective `sslmode` from a Postgres connection string. Honors the
17
+ * `sslmode` query param and the legacy `ssl=true` boolean. Returns `disable`
18
+ * when TLS is not requested.
19
+ */
20
+ export declare function sslModeFromConnectionString(connectionString: string): SslMode;
21
+ /**
22
+ * Resolve the `pg` ssl config for a connection string. See the module header
23
+ * for the full mode table. Returns `undefined` when TLS should be off.
24
+ */
25
+ export declare function resolveTlsConfig(connectionString: string, options?: TlsResolveOptions): PgSslConfig | undefined;
@@ -0,0 +1,12 @@
1
+ import type { TypedQueryClient } from "../generated/storage-kit/index.js";
2
+ export interface BackfillResult {
3
+ tenantsSeeded: number;
4
+ apiKeysBackfilled: number;
5
+ usersGrandfatheredVerified: number;
6
+ }
7
+ /**
8
+ * Seed tenants + ensure a signing key + stamp legacy api_keys to the root tenant.
9
+ * @param client live cloud Postgres client
10
+ * @param apiKeysTable the api_keys table name (from @hasna/contracts auth kit)
11
+ */
12
+ export declare function seedAndBackfill(client: TypedQueryClient, apiKeysTable: string): Promise<BackfillResult>;
@@ -0,0 +1,24 @@
1
+ /** RFC 4122 v5 (SHA-1) UUID from a name under a 16-byte namespace. */
2
+ export declare function uuidv5(name: string, namespace: Buffer): string;
3
+ /** Namespace for all hasna tenant ids: uuidv5(DNS, "hasna.xyz"). */
4
+ export declare const HASNA_TENANT_NAMESPACE: string;
5
+ /** Derive a stable tenant UUID from `tenant:<slug>:<kind>`. */
6
+ export declare function deriveTenantId(slug: string, kind: string): string;
7
+ /**
8
+ * The FIXED canonical root-tenant UUID (slug `hasna`, kind `root`) from the
9
+ * fleet auth/tenancy standard v2. Every app backfills pre-existing rows to this
10
+ * exact UUID. Verified in tests to equal the derived value.
11
+ */
12
+ export declare const ROOT_TENANT_ID = "adfd95c7-ee8b-52cb-ae47-4ae65dae3313";
13
+ export declare const ROOT_TENANT_SLUG = "hasna";
14
+ export interface SeedTenant {
15
+ id: string;
16
+ slug: string;
17
+ name: string;
18
+ kind: string;
19
+ parentId: string | null;
20
+ }
21
+ /** The root tenant + its brand children, all with fixed derived UUIDs. */
22
+ export declare const SEED_TENANTS: SeedTenant[];
23
+ /** Fresh random v4 id (users, sessions, service principals, key ids). */
24
+ export declare function newId(): string;
@@ -0,0 +1,58 @@
1
+ export interface ConfirmationEmail {
2
+ to: string;
3
+ code: string;
4
+ /** Fully-formed confirmation link (GET) for one-click confirm. */
5
+ link: string;
6
+ /** Minutes until the code/link expires (for the copy). */
7
+ expiresMinutes: number;
8
+ /** Why the code was issued (drives the subject/copy). Default `signup`. */
9
+ purpose?: "signup" | "login";
10
+ }
11
+ export interface SendResult {
12
+ messageId?: string;
13
+ skipped?: boolean;
14
+ reason?: string;
15
+ }
16
+ export interface Mailer {
17
+ sendConfirmation(input: ConfirmationEmail): Promise<SendResult>;
18
+ }
19
+ /** No-op mailer: used when email is disabled (local/dev). Never throws. */
20
+ export declare class NoopMailer implements Mailer {
21
+ sendConfirmation(_input: ConfirmationEmail): Promise<SendResult>;
22
+ }
23
+ interface AwsCredentials {
24
+ accessKeyId: string;
25
+ secretAccessKey: string;
26
+ sessionToken?: string;
27
+ }
28
+ export interface SesMailerOptions {
29
+ region: string;
30
+ /** Envelope From, e.g. `Hasna Tenants <auth@tenants.hasna.xyz>`. */
31
+ from: string;
32
+ /** Cross-account identity ARN authorizing this sender. */
33
+ fromArn?: string;
34
+ /** Explicit credentials (else resolved from the AWS chain at send time). */
35
+ credentials?: AwsCredentials;
36
+ }
37
+ /** SES (SESv2) sender using raw SigV4 — zero third-party dependencies. */
38
+ export declare class SesMailer implements Mailer {
39
+ private readonly options;
40
+ constructor(options: SesMailerOptions);
41
+ sendConfirmation(input: ConfirmationEmail): Promise<SendResult>;
42
+ }
43
+ export declare const MAIL_FROM_ENV = "HASNA_TENANTS_MAIL_FROM";
44
+ export declare const MAIL_ENABLED_ENV = "HASNA_TENANTS_EMAIL_ENABLED";
45
+ export declare const SES_REGION_ENV = "HASNA_TENANTS_SES_REGION";
46
+ export declare const SES_FROM_ARN_ENV = "HASNA_TENANTS_SES_FROM_ARN";
47
+ export declare const CONFIRM_URL_BASE_ENV = "HASNA_TENANTS_CONFIRM_URL_BASE";
48
+ export declare const DEFAULT_MAIL_FROM = "Hasna Tenants <auth@tenants.hasna.xyz>";
49
+ export declare const DEFAULT_CONFIRM_URL_BASE = "https://tenants.hasna.xyz";
50
+ /** The public base URL used to build one-click confirmation links. */
51
+ export declare function confirmUrlBaseFromEnv(env?: NodeJS.ProcessEnv): string;
52
+ /**
53
+ * Build a mailer from the environment. Returns a NoopMailer unless
54
+ * `HASNA_TENANTS_EMAIL_ENABLED=1`, so email stays inert in local/dev until
55
+ * explicitly turned on in the deployed service.
56
+ */
57
+ export declare function createMailerFromEnv(env?: NodeJS.ProcessEnv): Mailer;
58
+ export {};
@@ -0,0 +1,10 @@
1
+ import { type Migration } from "../generated/storage-kit/index.js";
2
+ export declare const TENANTS_TABLE = "tenants";
3
+ export declare const USERS_TABLE = "users";
4
+ export declare const SERVICE_PRINCIPALS_TABLE = "service_principals";
5
+ export declare const MEMBERSHIPS_TABLE = "memberships";
6
+ export declare const SESSIONS_TABLE = "sessions";
7
+ export declare const AUTH_CHALLENGES_TABLE = "auth_challenges";
8
+ export declare const JWT_SIGNING_KEYS_TABLE = "jwt_signing_keys";
9
+ /** Ordered, additive tenancy/IdP migrations. */
10
+ export declare function idpMigrations(apiKeysTable: string): Migration[];
@@ -0,0 +1,4 @@
1
+ export declare function hashPassword(password: string): string;
2
+ export declare function verifyPassword(password: string, stored: string): boolean;
3
+ /** sha256 hex — used for OTP codes and opaque session tokens at rest. */
4
+ export declare function sha256(value: string): string;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Built-in default allowlist: the hasna-branded apex domains from the live
3
+ * domains portfolio (ACTIVE `hasna.<tld>`, snapshot 2026-07-13). `hasna.xyz` is
4
+ * guaranteed present. Refreshable from the domains API; overridable via env.
5
+ */
6
+ export declare const DEFAULT_ALLOWED_EMAIL_DOMAINS: readonly string[];
7
+ export declare const ALLOWED_EMAIL_DOMAINS_ENV = "HASNA_TENANTS_ALLOWED_EMAIL_DOMAINS";
8
+ /** Extract the lowercased domain part of an email, or null when malformed. */
9
+ export declare function emailDomain(email: string): string | null;
10
+ export interface EmailPolicy {
11
+ /**
12
+ * Exact-match allowlist of permitted email domains. `null` DISABLES the check
13
+ * (allow any) — used only by tests/local; production always sets a Set.
14
+ */
15
+ allowedDomains: Set<string> | null;
16
+ /** Require email confirmation before a session/token is minted. */
17
+ requireConfirmation: boolean;
18
+ }
19
+ /**
20
+ * Build the email policy from the environment.
21
+ * - `HASNA_TENANTS_ALLOWED_EMAIL_DOMAINS` (comma list) overrides the default.
22
+ * - `HASNA_TENANTS_DISABLE_EMAIL_ALLOWLIST=1` fully disables it (escape hatch).
23
+ * - `HASNA_TENANTS_REQUIRE_EMAIL_CONFIRMATION=0` disables confirmation gating.
24
+ */
25
+ export declare function emailPolicyFromEnv(env?: NodeJS.ProcessEnv): EmailPolicy;
26
+ /** True when `email`'s domain is permitted by the policy (or the policy is off). */
27
+ export declare function isEmailDomainAllowed(email: string, policy: EmailPolicy): boolean;
@@ -0,0 +1,103 @@
1
+ import { type ApiKeyStore } from "@hasna/contracts/auth";
2
+ import type { IdpStoreApi } from "./store.js";
3
+ import { type JwksDocument } from "./tokens.js";
4
+ import { type EmailPolicy } from "./policy.js";
5
+ import { type Mailer } from "./mailer.js";
6
+ export declare const SESSION_TTL_SECONDS: number;
7
+ export declare const OTP_TTL_SECONDS: number;
8
+ export declare const OTP_MAX_ATTEMPTS = 5;
9
+ /**
10
+ * App slugs the IdP will mint fleet tokens for. `tenants` is this service's own
11
+ * app; the rest are the fleet apps that accept tenants-issued EdDSA tokens
12
+ * (verified offline via the published JWKS). `identities` (the SEPARATE agent
13
+ * registry) is listed as a token AUDIENCE only — this is a cross-service token
14
+ * grant, not a code dependency on that package.
15
+ */
16
+ export declare const FLEET_APPS: string[];
17
+ /** This service's own app slug (the only app whose HMAC secret it holds). */
18
+ export declare const SELF_APP = "tenants";
19
+ export declare class AuthError extends Error {
20
+ status: number;
21
+ code?: string | undefined;
22
+ details?: Record<string, unknown> | undefined;
23
+ constructor(status: number, message: string, code?: string | undefined, details?: Record<string, unknown> | undefined);
24
+ }
25
+ export interface AuthServiceOptions {
26
+ store: IdpStoreApi;
27
+ /** This service's own HMAC signing secret (mints self-app v1 keys only). */
28
+ signingSecret: string;
29
+ apiKeysTable: string;
30
+ /** Persists issued v1 HMAC keys (for the self-app token path). */
31
+ apiKeys?: ApiKeyStore;
32
+ /** Echo OTP codes in responses (DEV ONLY; never in prod). */
33
+ otpEcho?: boolean;
34
+ /** Login front-door policy (email allowlist + confirmation gate). */
35
+ emailPolicy?: EmailPolicy;
36
+ /** Sends confirmation / login codes (SES). Defaults to a no-op. */
37
+ mailer?: Mailer;
38
+ /** Public base URL for one-click confirmation links. */
39
+ confirmUrlBase?: string;
40
+ nowMs?: () => number;
41
+ }
42
+ export interface PrincipalSummary {
43
+ user_id: string;
44
+ kind: string;
45
+ email: string | null;
46
+ display_name: string | null;
47
+ }
48
+ export interface TenantMembershipSummary {
49
+ tenant_id: string;
50
+ role: string;
51
+ }
52
+ export declare class AuthService {
53
+ private readonly store;
54
+ private readonly signingSecret;
55
+ private readonly apiKeysTable;
56
+ private readonly apiKeys;
57
+ private readonly otpEcho;
58
+ private readonly emailPolicy;
59
+ private readonly mailer;
60
+ private readonly confirmUrlBase;
61
+ private readonly now;
62
+ constructor(options: AuthServiceOptions);
63
+ /** Reject any email whose domain is not on the (config-driven) allowlist. */
64
+ private assertEmailAllowed;
65
+ jwks(): Promise<JwksDocument>;
66
+ signup(input: {
67
+ email?: string;
68
+ name?: string;
69
+ kind?: string;
70
+ org_name?: string;
71
+ password?: string;
72
+ ip?: string | null;
73
+ userAgent?: string | null;
74
+ }): Promise<Record<string, unknown>>;
75
+ login(input: {
76
+ email?: string;
77
+ password?: string;
78
+ ip?: string | null;
79
+ userAgent?: string | null;
80
+ }): Promise<Record<string, unknown>>;
81
+ resend(input: {
82
+ email?: string;
83
+ }): Promise<Record<string, unknown>>;
84
+ verify(input: {
85
+ email?: string;
86
+ code?: string;
87
+ ip?: string | null;
88
+ userAgent?: string | null;
89
+ }): Promise<Record<string, unknown>>;
90
+ token(input: {
91
+ sessionToken: string;
92
+ app?: string;
93
+ scopes?: string[];
94
+ tenant_id?: string;
95
+ ttlSeconds?: number;
96
+ }): Promise<Record<string, unknown>>;
97
+ whoami(sessionToken: string): Promise<Record<string, unknown>>;
98
+ introspect(kid: string): Promise<Record<string, unknown>>;
99
+ private principalSummary;
100
+ private startChallenge;
101
+ private issueSession;
102
+ private resolveSession;
103
+ }
@@ -0,0 +1,205 @@
1
+ import type { TypedQueryClient } from "../generated/storage-kit/index.js";
2
+ import { type JwkPublic, type SigningKey } from "./tokens.js";
3
+ export declare const JWT_SIGNING_KEY_ENV = "HASNA_TENANTS_JWT_SIGNING_KEY";
4
+ export declare const JWT_KID_ENV = "HASNA_TENANTS_JWT_KID";
5
+ export interface TenantRow {
6
+ id: string;
7
+ slug: string;
8
+ name: string;
9
+ kind: string;
10
+ parent_id: string | null;
11
+ status: string;
12
+ identity_id: string | null;
13
+ }
14
+ export interface UserRow {
15
+ id: string;
16
+ kind: string;
17
+ email: string | null;
18
+ display_name: string | null;
19
+ identity_id: string | null;
20
+ home_tenant_id: string | null;
21
+ auth_method: string | null;
22
+ password_hash: string | null;
23
+ status: string;
24
+ email_verified_at: string | null;
25
+ }
26
+ export interface MembershipRow {
27
+ id: string;
28
+ tenant_id: string;
29
+ principal_id: string;
30
+ principal_type: string;
31
+ role: string;
32
+ scopes: unknown;
33
+ status: string;
34
+ }
35
+ export interface SessionRow {
36
+ id: string;
37
+ user_id: string | null;
38
+ tenant_id: string | null;
39
+ token_hash: string;
40
+ method: string | null;
41
+ issued_at: string;
42
+ expires_at: string | null;
43
+ revoked_at: string | null;
44
+ }
45
+ export interface SigningKeyRow {
46
+ kid: string;
47
+ alg: string;
48
+ public_jwk: unknown;
49
+ private_jwk: unknown;
50
+ status: string;
51
+ }
52
+ /** The subset of IdpStore the AuthService depends on (enables fakes in tests). */
53
+ export interface IdpStoreApi {
54
+ listPublicJwks(): Promise<JwkPublic[]>;
55
+ getSigningKeyForMinting(): Promise<SigningKey>;
56
+ getTenantBySlug(slug: string): Promise<TenantRow | null>;
57
+ createTenant(input: {
58
+ slug: string;
59
+ name: string;
60
+ kind?: string;
61
+ parentId?: string | null;
62
+ }): Promise<TenantRow>;
63
+ getUserByEmail(email: string): Promise<UserRow | null>;
64
+ getUserById(id: string): Promise<UserRow | null>;
65
+ markEmailVerified(userId: string): Promise<void>;
66
+ createUser(input: {
67
+ kind: string;
68
+ email?: string | null;
69
+ displayName?: string | null;
70
+ identityId?: string | null;
71
+ homeTenantId: string;
72
+ authMethod?: string | null;
73
+ passwordHash?: string | null;
74
+ }): Promise<UserRow>;
75
+ createMembership(input: {
76
+ tenantId: string;
77
+ principalId: string;
78
+ principalType: "user" | "service";
79
+ role: string;
80
+ scopes?: string[];
81
+ }): Promise<void>;
82
+ listMembershipsForPrincipal(principalId: string, principalType: "user" | "service"): Promise<MembershipRow[]>;
83
+ createSession(input: {
84
+ userId: string;
85
+ tenantId: string;
86
+ tokenHash: string;
87
+ method?: string;
88
+ expiresAt: Date;
89
+ ip?: string | null;
90
+ userAgent?: string | null;
91
+ }): Promise<string>;
92
+ getSessionByTokenHash(tokenHash: string): Promise<SessionRow | null>;
93
+ createChallenge(input: {
94
+ email: string;
95
+ codeHash: string;
96
+ purpose: string;
97
+ expiresAt: Date;
98
+ }): Promise<string>;
99
+ findActiveChallenge(email: string, purpose: string): Promise<{
100
+ id: string;
101
+ code_hash: string;
102
+ attempts: number;
103
+ } | null>;
104
+ consumeChallenge(id: string): Promise<void>;
105
+ bumpChallengeAttempts(id: string): Promise<void>;
106
+ recordApiKeyBinding(apiKeysTable: string, input: {
107
+ kid: string;
108
+ tenantId: string;
109
+ userId: string | null;
110
+ principalType: "user" | "service";
111
+ }): Promise<void>;
112
+ lookupApiKeyBinding(apiKeysTable: string, kid: string): Promise<{
113
+ tenant_id: string | null;
114
+ user_id: string | null;
115
+ principal_type: string | null;
116
+ } | null>;
117
+ }
118
+ export declare class IdpStore implements IdpStoreApi {
119
+ private readonly client;
120
+ constructor(client: TypedQueryClient);
121
+ /** Env-injected signing key (Secrets Manager path), or null. */
122
+ loadEnvSigningKey(env?: NodeJS.ProcessEnv): SigningKey | null;
123
+ /** The active DB signing key, generating + persisting one if none exists. */
124
+ getOrCreateActiveDbKey(): Promise<SigningKey>;
125
+ /** The key used to MINT new tokens (env wins, else the active DB key). */
126
+ getSigningKeyForMinting(): Promise<SigningKey>;
127
+ /** All public JWKs to publish/verify against (env + every active DB key). */
128
+ listPublicJwks(): Promise<JwkPublic[]>;
129
+ /** Idempotently seed the root tenant + brand children (fixed UUIDs). */
130
+ seedTenants(): Promise<void>;
131
+ getTenantById(id: string): Promise<TenantRow | null>;
132
+ getTenantBySlug(slug: string): Promise<TenantRow | null>;
133
+ createTenant(input: {
134
+ slug: string;
135
+ name: string;
136
+ kind?: string;
137
+ parentId?: string | null;
138
+ }): Promise<TenantRow>;
139
+ getUserByEmail(email: string): Promise<UserRow | null>;
140
+ getUserById(id: string): Promise<UserRow | null>;
141
+ /** Mark a user's email confirmed (idempotent; only stamps the first time). */
142
+ markEmailVerified(userId: string): Promise<void>;
143
+ createUser(input: {
144
+ kind: string;
145
+ email?: string | null;
146
+ displayName?: string | null;
147
+ identityId?: string | null;
148
+ homeTenantId: string;
149
+ authMethod?: string | null;
150
+ passwordHash?: string | null;
151
+ }): Promise<UserRow>;
152
+ setUserPassword(id: string, passwordHash: string): Promise<void>;
153
+ createMembership(input: {
154
+ tenantId: string;
155
+ principalId: string;
156
+ principalType: "user" | "service";
157
+ role: string;
158
+ scopes?: string[];
159
+ }): Promise<void>;
160
+ listMembershipsForPrincipal(principalId: string, principalType: "user" | "service"): Promise<MembershipRow[]>;
161
+ createServicePrincipal(input: {
162
+ id?: string;
163
+ tenantId: string;
164
+ kind?: string;
165
+ displayName?: string | null;
166
+ identityId?: string | null;
167
+ }): Promise<string>;
168
+ createSession(input: {
169
+ userId: string;
170
+ tenantId: string;
171
+ tokenHash: string;
172
+ method?: string;
173
+ expiresAt: Date;
174
+ ip?: string | null;
175
+ userAgent?: string | null;
176
+ }): Promise<string>;
177
+ getSessionByTokenHash(tokenHash: string): Promise<SessionRow | null>;
178
+ revokeSession(tokenHash: string): Promise<void>;
179
+ createChallenge(input: {
180
+ email: string;
181
+ codeHash: string;
182
+ purpose: string;
183
+ expiresAt: Date;
184
+ }): Promise<string>;
185
+ findActiveChallenge(email: string, purpose: string): Promise<{
186
+ id: string;
187
+ code_hash: string;
188
+ attempts: number;
189
+ } | null>;
190
+ consumeChallenge(id: string): Promise<void>;
191
+ bumpChallengeAttempts(id: string): Promise<void>;
192
+ /** Record an issued v1 HMAC key's tenant/user/principal binding at mint time. */
193
+ recordApiKeyBinding(apiKeysTable: string, input: {
194
+ kid: string;
195
+ tenantId: string;
196
+ userId: string | null;
197
+ principalType: "user" | "service";
198
+ }): Promise<void>;
199
+ /** Resolve a kid to its tenant binding (bridge lookup for v1 tokens). */
200
+ lookupApiKeyBinding(apiKeysTable: string, kid: string): Promise<{
201
+ tenant_id: string | null;
202
+ user_id: string | null;
203
+ principal_type: string | null;
204
+ } | null>;
205
+ }
@@ -0,0 +1,89 @@
1
+ import { type KeyObject } from "node:crypto";
2
+ export declare const TOKEN_TYPE = "at+jwt";
3
+ export declare const TOKEN_ALG = "EdDSA";
4
+ /**
5
+ * Fixed fleet issuer string embedded in every access token's `iss` claim and
6
+ * enforced on verify. This is a cross-fleet WIRE CONTRACT value pinned by all
7
+ * verifying apps — not a dependency on the @hasna/identities agent registry.
8
+ * Changing it requires a coordinated fleet-wide rollout.
9
+ */
10
+ export declare const TOKEN_ISSUER = "identities";
11
+ /** Default access-token TTL (24h) per standard §1.3 (v2 access tokens ≤24h). */
12
+ export declare const DEFAULT_ACCESS_TOKEN_TTL_SECONDS: number;
13
+ export interface AccessTokenClaims {
14
+ /** Issuer — the fixed fleet issuer string (see TOKEN_ISSUER). */
15
+ iss: string;
16
+ /** Audience — the app slug the token is for. */
17
+ aud: string;
18
+ /** Subject — principal id (users.id or service_principals.id). */
19
+ sub: string;
20
+ /** Tenant UUID (the isolation boundary). */
21
+ tid: string;
22
+ /** Principal type: user | service. */
23
+ pt: "user" | "service";
24
+ /** Granted scopes (`<app>:<action>` / `<app>:*` / `*`). */
25
+ scope: string[];
26
+ /** Issued-at, epoch seconds. */
27
+ iat: number;
28
+ /** Expiry, epoch seconds. */
29
+ exp: number;
30
+ /** Token id (revocation / audit). */
31
+ jti: string;
32
+ }
33
+ export interface JwkPublic {
34
+ kty: "OKP";
35
+ crv: "Ed25519";
36
+ x: string;
37
+ kid: string;
38
+ use: "sig";
39
+ alg: "EdDSA";
40
+ }
41
+ export interface JwksDocument {
42
+ keys: JwkPublic[];
43
+ }
44
+ /** A resolved signing key: private KeyObject + published public JWK + kid. */
45
+ export interface SigningKey {
46
+ kid: string;
47
+ privateKey: KeyObject;
48
+ publicJwk: JwkPublic;
49
+ }
50
+ /** Build a SigningKey from a private Ed25519 JWK (as stored / injected). */
51
+ export declare function signingKeyFromPrivateJwk(kid: string, privateJwk: Record<string, unknown>): SigningKey;
52
+ /** Assemble a JWKS document from one or more public JWKs. */
53
+ export declare function buildJwks(publicJwks: JwkPublic[]): JwksDocument;
54
+ export interface SignAccessTokenInput {
55
+ aud: string;
56
+ sub: string;
57
+ tid: string;
58
+ pt: "user" | "service";
59
+ scope: string[];
60
+ ttlSeconds?: number;
61
+ jti: string;
62
+ nowMs?: number;
63
+ }
64
+ /** Mint a signed EdDSA access token. */
65
+ export declare function signAccessToken(key: SigningKey, input: SignAccessTokenInput): {
66
+ token: string;
67
+ claims: AccessTokenClaims;
68
+ };
69
+ export type VerifyAccessTokenResult = {
70
+ ok: true;
71
+ claims: AccessTokenClaims;
72
+ kid: string;
73
+ } | {
74
+ ok: false;
75
+ reason: string;
76
+ };
77
+ export interface VerifyAccessTokenOptions {
78
+ /** Public JWKs to verify against (from a JWKS fetch / local key set). */
79
+ jwks: JwkPublic[];
80
+ /** Expected audience (this app slug). Enforced when set. */
81
+ expectedAudience?: string;
82
+ /** Clock-skew leeway in seconds. Default 0. */
83
+ leewaySeconds?: number;
84
+ nowMs?: number;
85
+ }
86
+ /** Fully verify an EdDSA access token: signature, issuer, audience, expiry. */
87
+ export declare function verifyAccessToken(token: string, options: VerifyAccessTokenOptions): VerifyAccessTokenResult;
88
+ /** Structural parse (no signature check) — used to route v1 HMAC vs v2 JWS. */
89
+ export declare function looksLikeAccessToken(token: string): boolean;
@@ -0,0 +1,18 @@
1
+ export * from "./idp/service.js";
2
+ export * from "./idp/store.js";
3
+ export * from "./idp/tokens.js";
4
+ export * from "./idp/ids.js";
5
+ export * from "./idp/passwords.js";
6
+ export * from "./idp/policy.js";
7
+ export * from "./idp/mailer.js";
8
+ export * from "./idp/backfill.js";
9
+ export * from "./idp/migrations.js";
10
+ export { API_KEYS_TABLE, tenantsMigrations } from "./migrations.js";
11
+ export { TENANTS_APP_NAME, createCloudClient, runTenantsMigrations, cloudHealth, cloudReady, } from "./db.js";
12
+ export type { TenantsCloud } from "./db.js";
13
+ export type { TypedQueryClient, PoolQueryClient } from "./generated/storage-kit/index.js";
14
+ export { buildOpenApiDocument } from "./server/openapi.js";
15
+ export type { TenantsOpenApiDocument } from "./server/openapi.js";
16
+ export { TENANTS_SERVE_APP, buildHandler, createFetchHandler, startServer, } from "./server/serve.js";
17
+ export type { ServeOptions, RunningServer } from "./server/serve.js";
18
+ export { getPackageVersion } from "./version.js";