@glinr/theauth 0.4.2
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.
- package/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/a2a/index.d.ts +2341 -0
- package/dist/a2a/index.js +826 -0
- package/dist/a2a/index.js.map +1 -0
- package/dist/agent/index.d.ts +32 -0
- package/dist/agent/index.js +795 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/audit/index.d.ts +24 -0
- package/dist/audit/index.js +641 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/auth/index.d.ts +3466 -0
- package/dist/auth/index.js +16139 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/crypto/index.d.ts +55 -0
- package/dist/crypto/index.js +186 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/index.d.ts +1711 -0
- package/dist/index.js +23160 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +274 -0
- package/dist/mcp/index.js +1262 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/permission/index.d.ts +89 -0
- package/dist/permission/index.js +801 -0
- package/dist/permission/index.js.map +1 -0
- package/dist/redirect/index.d.ts +118 -0
- package/dist/redirect/index.js +292 -0
- package/dist/redirect/index.js.map +1 -0
- package/dist/standards/index.d.ts +139 -0
- package/dist/standards/index.js +72 -0
- package/dist/standards/index.js.map +1 -0
- package/dist/types-BiUe9e8u.d.ts +426 -0
- package/dist/types-D1sBnWrs.d.ts +9636 -0
- package/dist/types-RJPOU4un.d.ts +9636 -0
- package/dist/vc/index.d.ts +989 -0
- package/dist/vc/index.js +692 -0
- package/dist/vc/index.js.map +1 -0
- package/package.json +139 -0
|
@@ -0,0 +1,3466 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { a5 as AuthAdapter, o as ResolvedUser, X as KavachPlugin, D as Database, _ as AdminConfig, p as SessionManager, a2 as ApiKeyManagerConfig, aa as EmailOtpConfig, P as Permission, ae as MagicLinkConfig, aj as OrgConfig, ao as PasskeyConfig, N as PluginEndpoint, aF as TotpConfig } from '../types-D1sBnWrs.js';
|
|
3
|
+
export { u as AdminModule, $ as AdminUser, a1 as ApiKey, v as ApiKeyManagerModule, a6 as CaptchaConfig, G as CaptchaModule, a7 as CaptchaVerifyResult, a8 as CreateTokenInput, E as EmailOtpModule, ab as EmailVerificationConfig, y as EmailVerificationModule, r as MagicLinkModule, ag as OidcProvider, ah as OneTimeTokenConfig, z as OneTimeTokenModule, ai as OneTimeTokenPurpose, ak as OrgInvitation, al as OrgMember, O as OrgModule, am as OrgRole, an as Organization, ap as PasskeyCredential, s as PasskeyModule, aq as PasswordResetConfig, x as PasswordResetModule, as as PhoneAuthConfig, F as PhoneAuthModule, av as RevokeTokensResult, aw as SSO_ERROR, ax as SamlProvider, aA as SsoAuditEvent, aB as SsoConfig, aC as SsoConnection, aD as SsoError, t as SsoModule, T as TotpModule, aG as TotpSetup, aH as UsernameAuthConfig, w as UsernameAuthModule, aI as ValidateTokenResult, bu as WebhookConfig, bv as WebhookEvent, W as WebhookModule, aS as createAdminModule, aT as createApiKeyManagerModule, aV as createCaptchaModule, aY as createEmailOtpModule, aZ as createEmailVerificationModule, a_ as createMagicLinkModule, a$ as createOneTimeTokenModule, b0 as createOrgModule, b1 as createPasskeyModule, b2 as createPasswordResetModule, b3 as createPhoneAuthModule, b6 as createSsoModule, b7 as createTotpModule, b8 as createUsernameAuthModule, bw as createWebhookModule } from '../types-D1sBnWrs.js';
|
|
4
|
+
import { R as Result } from '../types-BiUe9e8u.js';
|
|
5
|
+
import { AgentType, TrustTier } from '../standards/index.js';
|
|
6
|
+
import * as jose from 'jose';
|
|
7
|
+
import 'drizzle-orm/sqlite-core';
|
|
8
|
+
import '../redirect/index.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* JWT bearer-token auth adapter.
|
|
12
|
+
*
|
|
13
|
+
* Verifies a JWT from the `Authorization: Bearer <token>` header using a
|
|
14
|
+
* symmetric secret (HS256/HS384/HS512). Extracts `sub`, `email`, `name`, and
|
|
15
|
+
* `picture` from the payload and returns them as a `ResolvedUser`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { bearerAuth } from '@glinr/theauth/auth';
|
|
20
|
+
*
|
|
21
|
+
* const adapter = bearerAuth({
|
|
22
|
+
* secret: process.env.JWT_SECRET,
|
|
23
|
+
* issuer: 'https://my-app.example.com',
|
|
24
|
+
* audience: 'theauth',
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
declare const BearerAuthOptionsSchema: z.ZodObject<{
|
|
30
|
+
/**
|
|
31
|
+
* Secret used to verify HS256/HS384/HS512 tokens.
|
|
32
|
+
* Must be at least 32 characters.
|
|
33
|
+
*/
|
|
34
|
+
secret: z.ZodString;
|
|
35
|
+
/** Expected `iss` claim. Omit to skip issuer validation. */
|
|
36
|
+
issuer: z.ZodOptional<z.ZodString>;
|
|
37
|
+
/** Expected `aud` claim. Omit to skip audience validation. */
|
|
38
|
+
audience: z.ZodOptional<z.ZodString>;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
secret: string;
|
|
41
|
+
issuer?: string | undefined;
|
|
42
|
+
audience?: string | undefined;
|
|
43
|
+
}, {
|
|
44
|
+
secret: string;
|
|
45
|
+
issuer?: string | undefined;
|
|
46
|
+
audience?: string | undefined;
|
|
47
|
+
}>;
|
|
48
|
+
type BearerAuthOptions = z.infer<typeof BearerAuthOptionsSchema>;
|
|
49
|
+
/**
|
|
50
|
+
* Create an `AuthAdapter` that validates a JWT from the `Authorization: Bearer`
|
|
51
|
+
* header and maps its claims to a `ResolvedUser`.
|
|
52
|
+
*
|
|
53
|
+
* Returns `null` when:
|
|
54
|
+
* - No `Authorization` header is present
|
|
55
|
+
* - The header does not use the `Bearer` scheme
|
|
56
|
+
* - The JWT signature is invalid, the token is expired, or claims do not match
|
|
57
|
+
* the configured `issuer` / `audience`
|
|
58
|
+
*/
|
|
59
|
+
declare function bearerAuth(options: BearerAuthOptions): AuthAdapter;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Custom resolver auth adapter.
|
|
63
|
+
*
|
|
64
|
+
* Wraps an arbitrary resolver function as an `AuthAdapter`. Use this when
|
|
65
|
+
* you need to integrate with an auth provider that does not have a built-in
|
|
66
|
+
* adapter (e.g. better-auth, Auth.js, Clerk, Supabase Auth, etc.).
|
|
67
|
+
*
|
|
68
|
+
* @example Clerk session cookie
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import { customAuth } from '@glinr/theauth/auth';
|
|
71
|
+
* import { clerkClient } from '@clerk/clerk-sdk-node';
|
|
72
|
+
*
|
|
73
|
+
* const adapter = customAuth(async (request) => {
|
|
74
|
+
* const sessionToken = request.headers.get('cookie')
|
|
75
|
+
* ?.split('; ')
|
|
76
|
+
* .find(c => c.startsWith('__session='))
|
|
77
|
+
* ?.split('=')[1];
|
|
78
|
+
*
|
|
79
|
+
* if (!sessionToken) return null;
|
|
80
|
+
*
|
|
81
|
+
* const session = await clerkClient.sessions.verifySession('', sessionToken);
|
|
82
|
+
* return { id: session.userId };
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example better-auth
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { customAuth } from '@glinr/theauth/auth';
|
|
89
|
+
* import { auth } from './lib/auth'; // your better-auth instance
|
|
90
|
+
*
|
|
91
|
+
* const adapter = customAuth(async (request) => {
|
|
92
|
+
* const session = await auth.api.getSession({ headers: request.headers });
|
|
93
|
+
* if (!session?.user) return null;
|
|
94
|
+
* return {
|
|
95
|
+
* id: session.user.id,
|
|
96
|
+
* email: session.user.email ?? undefined,
|
|
97
|
+
* name: session.user.name ?? undefined,
|
|
98
|
+
* image: session.user.image ?? undefined,
|
|
99
|
+
* };
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create an `AuthAdapter` from a custom resolver function.
|
|
106
|
+
*
|
|
107
|
+
* The resolver receives the raw `Request` and must return either a
|
|
108
|
+
* `ResolvedUser` or `null` (unauthenticated).
|
|
109
|
+
*/
|
|
110
|
+
declare function customAuth(resolver: (request: Request) => Promise<ResolvedUser | null>): AuthAdapter;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Header-based auth adapter.
|
|
114
|
+
*
|
|
115
|
+
* Extracts the user ID from a trusted request header. Designed for services
|
|
116
|
+
* deployed behind an auth proxy (e.g. Nginx, Cloudflare Access, AWS ALB) that
|
|
117
|
+
* injects a verified user-identity header before forwarding requests.
|
|
118
|
+
*
|
|
119
|
+
* @example Default header (`X-User-Id`)
|
|
120
|
+
* ```typescript
|
|
121
|
+
* import { headerAuth } from '@glinr/theauth/auth';
|
|
122
|
+
*
|
|
123
|
+
* const adapter = headerAuth();
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @example Custom header
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const adapter = headerAuth({ header: 'X-Authenticated-User' });
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* IMPORTANT: Only use this adapter when the header cannot be forged by the
|
|
132
|
+
* client (i.e. the upstream proxy strips or overrides it).
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
interface HeaderAuthOptions {
|
|
136
|
+
/**
|
|
137
|
+
* Name of the HTTP header that carries the user ID.
|
|
138
|
+
* Defaults to `X-User-Id`.
|
|
139
|
+
*/
|
|
140
|
+
header?: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create an `AuthAdapter` that extracts the user identity from a request header.
|
|
144
|
+
*
|
|
145
|
+
* Returns `null` when the header is absent or its value is empty.
|
|
146
|
+
*/
|
|
147
|
+
declare function headerAuth(options?: HeaderAuthOptions): AuthAdapter;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Additional user/session fields plugin for TheAuth.
|
|
151
|
+
*
|
|
152
|
+
* Lets callers extend the user and session schemas with typed custom fields
|
|
153
|
+
* without writing any database migrations. Fields are stored in the existing
|
|
154
|
+
* `user.metadata` or `session.metadata` JSON columns.
|
|
155
|
+
*
|
|
156
|
+
* Field types: `string`, `number`, `boolean`, `json`.
|
|
157
|
+
* Required fields must be present when calling `setUserFields`.
|
|
158
|
+
* Fields not in the schema are rejected during `validate()`.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* import { createKavach } from '@glinr/theauth';
|
|
163
|
+
* import { additionalFields } from '@glinr/theauth/auth';
|
|
164
|
+
*
|
|
165
|
+
* const kavach = await createKavach({
|
|
166
|
+
* database: { provider: 'sqlite', url: 'kavach.db' },
|
|
167
|
+
* plugins: [
|
|
168
|
+
* additionalFields({
|
|
169
|
+
* user: {
|
|
170
|
+
* plan: { type: 'string', required: false, defaultValue: 'free' },
|
|
171
|
+
* credits: { type: 'number', required: false, defaultValue: 0 },
|
|
172
|
+
* },
|
|
173
|
+
* }),
|
|
174
|
+
* ],
|
|
175
|
+
* });
|
|
176
|
+
*
|
|
177
|
+
* const mod = kavach.plugins.getContext().additionalFields as AdditionalFieldsModule;
|
|
178
|
+
* await mod.setUserFields(userId, { plan: 'pro', credits: 100 });
|
|
179
|
+
* const fields = await mod.getUserFields(userId);
|
|
180
|
+
* // => { plan: 'pro', credits: 100 }
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
interface FieldDefinition {
|
|
185
|
+
type: "string" | "number" | "boolean" | "json";
|
|
186
|
+
required?: boolean;
|
|
187
|
+
defaultValue?: unknown;
|
|
188
|
+
}
|
|
189
|
+
interface AdditionalFieldsConfig {
|
|
190
|
+
/** Custom fields for users (stored in user.metadata). */
|
|
191
|
+
user?: Record<string, FieldDefinition>;
|
|
192
|
+
/** Custom fields for sessions (stored in session.metadata). */
|
|
193
|
+
session?: Record<string, FieldDefinition>;
|
|
194
|
+
}
|
|
195
|
+
interface ValidationResult {
|
|
196
|
+
valid: boolean;
|
|
197
|
+
errors?: string[];
|
|
198
|
+
}
|
|
199
|
+
interface AdditionalFieldsModule {
|
|
200
|
+
/**
|
|
201
|
+
* Return the additional fields stored in `user.metadata` for the given user.
|
|
202
|
+
*
|
|
203
|
+
* Fields missing from the stored metadata are filled with their `defaultValue`
|
|
204
|
+
* (if one is defined in the schema).
|
|
205
|
+
*/
|
|
206
|
+
getUserFields(userId: string): Promise<Record<string, unknown>>;
|
|
207
|
+
/**
|
|
208
|
+
* Write `fields` into `user.metadata`, merging with any already-stored values.
|
|
209
|
+
*
|
|
210
|
+
* Validates against the `user` schema before writing.
|
|
211
|
+
* Throws when the user does not exist or validation fails.
|
|
212
|
+
*/
|
|
213
|
+
setUserFields(userId: string, fields: Record<string, unknown>): Promise<void>;
|
|
214
|
+
/**
|
|
215
|
+
* Return the additional fields stored in `session.metadata` for the given session.
|
|
216
|
+
*/
|
|
217
|
+
getSessionFields(sessionId: string): Promise<Record<string, unknown>>;
|
|
218
|
+
/**
|
|
219
|
+
* Write `fields` into `session.metadata`, merging with any already-stored values.
|
|
220
|
+
*
|
|
221
|
+
* Validates against the `session` schema before writing.
|
|
222
|
+
* Throws when the session does not exist or validation fails.
|
|
223
|
+
*/
|
|
224
|
+
setSessionFields(sessionId: string, fields: Record<string, unknown>): Promise<void>;
|
|
225
|
+
/**
|
|
226
|
+
* Validate a field map against the "user" or "session" schema.
|
|
227
|
+
*
|
|
228
|
+
* Returns `{ valid: true }` on success or `{ valid: false, errors: [...] }` on
|
|
229
|
+
* failure. Does not throw.
|
|
230
|
+
*/
|
|
231
|
+
validate(fields: Record<string, unknown>, schema: "user" | "session"): ValidationResult;
|
|
232
|
+
}
|
|
233
|
+
declare function createAdditionalFieldsModule(config: AdditionalFieldsConfig, db: Database): AdditionalFieldsModule;
|
|
234
|
+
declare function additionalFields(config?: AdditionalFieldsConfig): KavachPlugin;
|
|
235
|
+
|
|
236
|
+
declare function admin(config?: AdminConfig): KavachPlugin;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Anonymous authentication for TheAuth.
|
|
240
|
+
*
|
|
241
|
+
* Lets users start as guests without providing credentials. The anonymous
|
|
242
|
+
* user can later be upgraded to a real account by supplying an email.
|
|
243
|
+
*
|
|
244
|
+
* Anonymous users are stored in `kavach_users` with a synthetic placeholder
|
|
245
|
+
* email (`anon_<uuid>@theauth.anonymous`) and a metadata flag
|
|
246
|
+
* `{ anonymous: true }`. This satisfies the NOT NULL UNIQUE constraint on
|
|
247
|
+
* the email column while keeping them easily identifiable.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* const anon = createAnonymousAuthModule(config, db, sessionManager);
|
|
252
|
+
*
|
|
253
|
+
* // On first visit
|
|
254
|
+
* const { userId, sessionToken } = await anon.createAnonymousUser();
|
|
255
|
+
*
|
|
256
|
+
* // Later, when user signs up
|
|
257
|
+
* await anon.upgradeUser(userId, { email: 'alice@example.com', name: 'Alice' });
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
interface AnonymousAuthConfig {
|
|
262
|
+
/** How long anonymous sessions last in seconds (default: 86400 = 24 hours) */
|
|
263
|
+
sessionTtlSeconds?: number;
|
|
264
|
+
/** Whether to allow anonymous users to create agents (default: false) */
|
|
265
|
+
allowAgentCreation?: boolean;
|
|
266
|
+
}
|
|
267
|
+
interface AnonymousAuthModule {
|
|
268
|
+
/** Create an anonymous user and a session. */
|
|
269
|
+
createAnonymousUser(): Promise<{
|
|
270
|
+
userId: string;
|
|
271
|
+
sessionToken: string;
|
|
272
|
+
}>;
|
|
273
|
+
/** Upgrade an anonymous user to a real account by setting their email. */
|
|
274
|
+
upgradeUser(anonymousUserId: string, upgrade: {
|
|
275
|
+
email: string;
|
|
276
|
+
name?: string;
|
|
277
|
+
}): Promise<void>;
|
|
278
|
+
/** Check if a user was created as anonymous and has not been upgraded. */
|
|
279
|
+
isAnonymous(userId: string): Promise<boolean>;
|
|
280
|
+
/**
|
|
281
|
+
* Delete anonymous users older than `maxAgeMs` and their sessions.
|
|
282
|
+
* Returns the number of users removed.
|
|
283
|
+
*/
|
|
284
|
+
cleanup(maxAgeMs?: number): Promise<number>;
|
|
285
|
+
}
|
|
286
|
+
declare function createAnonymousAuthModule(config: AnonymousAuthConfig, db: Database, sessionManager: SessionManager): AnonymousAuthModule;
|
|
287
|
+
|
|
288
|
+
declare function anonymousAuth(config?: AnonymousAuthConfig): KavachPlugin;
|
|
289
|
+
|
|
290
|
+
declare function apiKeys(config?: ApiKeyManagerConfig): KavachPlugin;
|
|
291
|
+
|
|
292
|
+
interface CostAttributionConfig {
|
|
293
|
+
/** ISO 4217 currency code, default 'USD' */
|
|
294
|
+
currency?: string;
|
|
295
|
+
/** Dollar amounts that trigger alerts */
|
|
296
|
+
alertThresholds?: {
|
|
297
|
+
warn: number;
|
|
298
|
+
critical: number;
|
|
299
|
+
};
|
|
300
|
+
/** Called when a threshold is crossed or budget exceeded */
|
|
301
|
+
onAlert?: (alert: CostAlert) => void | Promise<void>;
|
|
302
|
+
/** How many days of events to keep, default 90 */
|
|
303
|
+
retentionDays?: number;
|
|
304
|
+
}
|
|
305
|
+
interface RecordCostInput {
|
|
306
|
+
agentId: string;
|
|
307
|
+
/** e.g. 'openai:gpt-4o', 'anthropic:claude-3-5-sonnet', 'mcp:github' */
|
|
308
|
+
tool: string;
|
|
309
|
+
inputTokens?: number;
|
|
310
|
+
outputTokens?: number;
|
|
311
|
+
costUsd: number;
|
|
312
|
+
metadata?: Record<string, unknown>;
|
|
313
|
+
/** Attribute to a delegation chain */
|
|
314
|
+
delegationChainId?: string;
|
|
315
|
+
}
|
|
316
|
+
interface CostReport {
|
|
317
|
+
agentId: string;
|
|
318
|
+
period: {
|
|
319
|
+
start: Date;
|
|
320
|
+
end: Date;
|
|
321
|
+
};
|
|
322
|
+
totalCostUsd: number;
|
|
323
|
+
byTool: Array<{
|
|
324
|
+
tool: string;
|
|
325
|
+
costUsd: number;
|
|
326
|
+
callCount: number;
|
|
327
|
+
}>;
|
|
328
|
+
byDay: Array<{
|
|
329
|
+
date: string;
|
|
330
|
+
costUsd: number;
|
|
331
|
+
}>;
|
|
332
|
+
}
|
|
333
|
+
interface CostAlert {
|
|
334
|
+
type: "warn" | "critical" | "budget_exceeded";
|
|
335
|
+
agentId: string;
|
|
336
|
+
currentCostUsd: number;
|
|
337
|
+
threshold: number;
|
|
338
|
+
period: string;
|
|
339
|
+
}
|
|
340
|
+
interface BudgetCheckResult {
|
|
341
|
+
withinBudget: boolean;
|
|
342
|
+
spent: number;
|
|
343
|
+
limit: number | null;
|
|
344
|
+
remaining: number | null;
|
|
345
|
+
}
|
|
346
|
+
interface CostAttributionModule {
|
|
347
|
+
recordCost(input: RecordCostInput): Promise<Result<void>>;
|
|
348
|
+
getAgentCost(agentId: string, period?: {
|
|
349
|
+
start: Date;
|
|
350
|
+
end: Date;
|
|
351
|
+
}): Promise<Result<CostReport>>;
|
|
352
|
+
getOwnerCost(ownerId: string, period?: {
|
|
353
|
+
start: Date;
|
|
354
|
+
end: Date;
|
|
355
|
+
}): Promise<Result<CostReport>>;
|
|
356
|
+
getTopAgentsByCost(limit?: number, period?: {
|
|
357
|
+
start: Date;
|
|
358
|
+
end: Date;
|
|
359
|
+
}): Promise<Result<Array<{
|
|
360
|
+
agentId: string;
|
|
361
|
+
totalCostUsd: number;
|
|
362
|
+
}>>>;
|
|
363
|
+
getDelegationChainCost(chainId: string): Promise<Result<CostReport>>;
|
|
364
|
+
checkBudget(agentId: string): Promise<Result<BudgetCheckResult>>;
|
|
365
|
+
cleanup(options?: {
|
|
366
|
+
retentionDays?: number;
|
|
367
|
+
}): Promise<Result<{
|
|
368
|
+
deleted: number;
|
|
369
|
+
}>>;
|
|
370
|
+
}
|
|
371
|
+
declare function createCostAttributionModule(db: Database, config?: CostAttributionConfig): CostAttributionModule;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Custom session fields plugin for TheAuth.
|
|
375
|
+
*
|
|
376
|
+
* Lets callers attach arbitrary data to sessions at creation time and read or
|
|
377
|
+
* update that data later. Everything is stored in the existing
|
|
378
|
+
* `session.metadata.custom` sub-key — no new database columns are required.
|
|
379
|
+
*
|
|
380
|
+
* Two integration points:
|
|
381
|
+
*
|
|
382
|
+
* 1. `defaultFields` — merged into every new session automatically.
|
|
383
|
+
* 2. `onSessionCreate` — async callback that receives the userId (and
|
|
384
|
+
* optionally the originating Request) and returns additional fields to
|
|
385
|
+
* merge. Runs once per session, during the plugin's `onSessionCreate` hook.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* import { createKavach } from '@glinr/theauth';
|
|
390
|
+
* import { customSession } from '@glinr/theauth/auth';
|
|
391
|
+
*
|
|
392
|
+
* const kavach = await createKavach({
|
|
393
|
+
* database: { provider: 'sqlite', url: 'kavach.db' },
|
|
394
|
+
* auth: { session: { secret: process.env.SESSION_SECRET } },
|
|
395
|
+
* plugins: [
|
|
396
|
+
* customSession({
|
|
397
|
+
* defaultFields: { theme: 'dark' },
|
|
398
|
+
* onSessionCreate: async (userId) => ({ lastSeen: Date.now() }),
|
|
399
|
+
* }),
|
|
400
|
+
* ],
|
|
401
|
+
* });
|
|
402
|
+
*
|
|
403
|
+
* // After a session is created via kavach.auth.session.create(...)
|
|
404
|
+
* const mod = kavach.plugins.getContext().customSession as CustomSessionModule;
|
|
405
|
+
* const fields = await mod.getSessionFields(session.id);
|
|
406
|
+
* // => { theme: 'dark', lastSeen: 1234567890 }
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
|
|
410
|
+
interface CustomSessionConfig {
|
|
411
|
+
/** Fields merged into every new session's metadata.custom on creation. */
|
|
412
|
+
defaultFields?: Record<string, unknown>;
|
|
413
|
+
/**
|
|
414
|
+
* Hook called when a new session is being created.
|
|
415
|
+
*
|
|
416
|
+
* The return value is merged into `session.metadata.custom` alongside any
|
|
417
|
+
* `defaultFields`. If both define the same key, the hook's value wins.
|
|
418
|
+
*/
|
|
419
|
+
onSessionCreate?: (userId: string, request?: Request) => Promise<Record<string, unknown>>;
|
|
420
|
+
}
|
|
421
|
+
interface CustomSessionModule {
|
|
422
|
+
/**
|
|
423
|
+
* Return the custom fields stored in `session.metadata.custom`.
|
|
424
|
+
*
|
|
425
|
+
* Returns `null` when the session does not exist or has no custom data.
|
|
426
|
+
*/
|
|
427
|
+
getSessionFields(sessionId: string): Promise<Record<string, unknown> | null>;
|
|
428
|
+
/**
|
|
429
|
+
* Merge `fields` into `session.metadata.custom`, overwriting any keys that
|
|
430
|
+
* already exist. Existing keys not present in `fields` are preserved.
|
|
431
|
+
*/
|
|
432
|
+
updateSessionFields(sessionId: string, fields: Record<string, unknown>): Promise<void>;
|
|
433
|
+
}
|
|
434
|
+
declare function createCustomSessionModule(_config: CustomSessionConfig, db: Database): CustomSessionModule;
|
|
435
|
+
declare function customSession(config?: CustomSessionConfig): KavachPlugin;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* OAuth Device Authorization Grant (RFC 8628) for TheAuth.
|
|
439
|
+
*
|
|
440
|
+
* Supports TVs, CLI tools, smart displays, and any device where the user
|
|
441
|
+
* cannot easily type a URL or complete an interactive login flow. The device
|
|
442
|
+
* requests a short code, the user approves on a secondary device (phone /
|
|
443
|
+
* browser), and the original device polls until authorization is granted.
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* const deviceAuth = createDeviceAuthModule({
|
|
448
|
+
* verificationUri: 'https://example.com/device',
|
|
449
|
+
* });
|
|
450
|
+
*
|
|
451
|
+
* // 1. CLI tool requests codes
|
|
452
|
+
* const { userCode, verificationUri } = await deviceAuth.requestCode();
|
|
453
|
+
* console.log(`Visit ${verificationUri} and enter: ${userCode}`);
|
|
454
|
+
*
|
|
455
|
+
* // 2. Poll from CLI
|
|
456
|
+
* const status = await deviceAuth.checkAuthorization(deviceCode);
|
|
457
|
+
*
|
|
458
|
+
* // 3. User approves on browser after logging in
|
|
459
|
+
* await deviceAuth.authorize(userCode, userId);
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
interface DeviceAuthConfig {
|
|
463
|
+
/** Code length for the human-readable user code segment (default: 4, produces "XXXX-XXXX") */
|
|
464
|
+
codeLength?: number;
|
|
465
|
+
/** Code expiry in seconds (default: 900 = 15 min) */
|
|
466
|
+
codeExpirySeconds?: number;
|
|
467
|
+
/** Polling interval in seconds (default: 5) */
|
|
468
|
+
pollIntervalSeconds?: number;
|
|
469
|
+
/** Verification URL shown to user */
|
|
470
|
+
verificationUri: string;
|
|
471
|
+
}
|
|
472
|
+
interface DeviceCodeResponse {
|
|
473
|
+
deviceCode: string;
|
|
474
|
+
userCode: string;
|
|
475
|
+
verificationUri: string;
|
|
476
|
+
verificationUriComplete: string;
|
|
477
|
+
expiresIn: number;
|
|
478
|
+
interval: number;
|
|
479
|
+
}
|
|
480
|
+
type DeviceAuthStatus = {
|
|
481
|
+
status: "pending";
|
|
482
|
+
} | {
|
|
483
|
+
status: "authorized";
|
|
484
|
+
userId: string;
|
|
485
|
+
} | {
|
|
486
|
+
status: "expired";
|
|
487
|
+
} | {
|
|
488
|
+
status: "denied";
|
|
489
|
+
};
|
|
490
|
+
interface DeviceAuthModule {
|
|
491
|
+
/** Start device auth flow: returns device_code, user_code, verification_uri */
|
|
492
|
+
requestCode(): Promise<DeviceCodeResponse>;
|
|
493
|
+
/** Check if user has authorized (called by polling device) */
|
|
494
|
+
checkAuthorization(deviceCode: string): Promise<DeviceAuthStatus>;
|
|
495
|
+
/** Authorize a device (called after user logs in on phone/browser) */
|
|
496
|
+
authorize(userCode: string, userId: string): Promise<void>;
|
|
497
|
+
/** Deny a device code (user explicitly rejects) */
|
|
498
|
+
deny(userCode: string): Promise<void>;
|
|
499
|
+
/** Handle HTTP requests for the device auth endpoints */
|
|
500
|
+
handleRequest(request: Request): Promise<Response | null>;
|
|
501
|
+
}
|
|
502
|
+
declare function createDeviceAuthModule(config: DeviceAuthConfig): DeviceAuthModule;
|
|
503
|
+
|
|
504
|
+
declare function deviceAuth(config: DeviceAuthConfig): KavachPlugin;
|
|
505
|
+
|
|
506
|
+
declare function emailOtp(config: EmailOtpConfig): KavachPlugin;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Ephemeral agent sessions for TheAuth.
|
|
510
|
+
*
|
|
511
|
+
* Short-lived, auto-expiring agent credentials for single-task use. Designed
|
|
512
|
+
* for computer-use agents (Claude, GPT with browsing, operator loops) that
|
|
513
|
+
* should not hold persistent tokens across invocations.
|
|
514
|
+
*
|
|
515
|
+
* Each session spins up a temporary agent, issues a bounded bearer token, and
|
|
516
|
+
* tracks how many actions have been consumed. When the TTL lapses or the
|
|
517
|
+
* action budget is exhausted the token becomes invalid and the underlying
|
|
518
|
+
* agent is automatically revoked.
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```typescript
|
|
522
|
+
* const mod = createEphemeralSessionModule({ db });
|
|
523
|
+
*
|
|
524
|
+
* // Create a 5-minute, 10-action session
|
|
525
|
+
* const result = await mod.createSession({
|
|
526
|
+
* ownerId: 'user-123',
|
|
527
|
+
* permissions: [{ resource: 'tool:browser', actions: ['navigate', 'click'] }],
|
|
528
|
+
* ttlSeconds: 300,
|
|
529
|
+
* maxActions: 10,
|
|
530
|
+
* });
|
|
531
|
+
*
|
|
532
|
+
* if (!result.success) throw new Error(result.error.message);
|
|
533
|
+
*
|
|
534
|
+
* const { token } = result.data;
|
|
535
|
+
*
|
|
536
|
+
* // Each time the agent performs an action
|
|
537
|
+
* await mod.consumeAction(token);
|
|
538
|
+
* ```
|
|
539
|
+
*/
|
|
540
|
+
|
|
541
|
+
interface EphemeralSessionConfig {
|
|
542
|
+
db: Database;
|
|
543
|
+
/** Default TTL for sessions in seconds (default: 300 = 5 min) */
|
|
544
|
+
defaultTtlSeconds?: number;
|
|
545
|
+
/** Hard ceiling on TTL in seconds (default: 3600 = 1 hour) */
|
|
546
|
+
maxTtlSeconds?: number;
|
|
547
|
+
/** Automatically revoke the underlying agent when the session expires (default: true) */
|
|
548
|
+
autoRevokeOnExpiry?: boolean;
|
|
549
|
+
/** Group all actions under a shared audit session ID (default: true) */
|
|
550
|
+
auditGrouping?: boolean;
|
|
551
|
+
}
|
|
552
|
+
interface CreateEphemeralSessionInput {
|
|
553
|
+
ownerId: string;
|
|
554
|
+
name?: string;
|
|
555
|
+
permissions: Permission[];
|
|
556
|
+
/** Seconds until the session expires (capped at maxTtlSeconds) */
|
|
557
|
+
ttlSeconds?: number;
|
|
558
|
+
/** Optional cap on the number of actions the token may authorize */
|
|
559
|
+
maxActions?: number;
|
|
560
|
+
metadata?: Record<string, unknown>;
|
|
561
|
+
}
|
|
562
|
+
interface EphemeralSession {
|
|
563
|
+
sessionId: string;
|
|
564
|
+
agentId: string;
|
|
565
|
+
/** Bearer token — shown once, never stored in plain text */
|
|
566
|
+
token: string;
|
|
567
|
+
expiresAt: Date;
|
|
568
|
+
maxActions: number | null;
|
|
569
|
+
actionsUsed: number;
|
|
570
|
+
status: "active" | "expired" | "exhausted" | "revoked";
|
|
571
|
+
/** Shared audit group ID for all actions within the session */
|
|
572
|
+
auditGroupId: string;
|
|
573
|
+
createdAt: Date;
|
|
574
|
+
}
|
|
575
|
+
interface EphemeralSessionValidateResult {
|
|
576
|
+
sessionId: string;
|
|
577
|
+
agentId: string;
|
|
578
|
+
remainingActions: number | null;
|
|
579
|
+
/** Seconds until the token expires */
|
|
580
|
+
expiresIn: number;
|
|
581
|
+
auditGroupId: string;
|
|
582
|
+
}
|
|
583
|
+
interface EphemeralSessionModule {
|
|
584
|
+
createSession(input: CreateEphemeralSessionInput): Promise<Result<EphemeralSession>>;
|
|
585
|
+
validateSession(token: string): Promise<Result<EphemeralSessionValidateResult>>;
|
|
586
|
+
consumeAction(token: string): Promise<Result<{
|
|
587
|
+
actionsRemaining: number | null;
|
|
588
|
+
}>>;
|
|
589
|
+
revokeSession(sessionId: string): Promise<Result<void>>;
|
|
590
|
+
listActiveSessions(ownerId: string): Promise<Result<EphemeralSession[]>>;
|
|
591
|
+
cleanupExpired(): Promise<Result<{
|
|
592
|
+
count: number;
|
|
593
|
+
}>>;
|
|
594
|
+
}
|
|
595
|
+
declare function createEphemeralSessionModule(config: EphemeralSessionConfig): EphemeralSessionModule;
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Real-time event streaming via Server-Sent Events (SSE) for TheAuth.
|
|
599
|
+
*
|
|
600
|
+
* Provides a persistent connection feed of audit events, agent lifecycle
|
|
601
|
+
* changes, auth events, and anomalies. SOC teams and monitoring systems can
|
|
602
|
+
* subscribe instead of polling the audit API or relying solely on webhooks.
|
|
603
|
+
*
|
|
604
|
+
* Endpoint: GET /api/kavach/events/stream
|
|
605
|
+
* Auth: Bearer token via Authorization header or `?token=` query param
|
|
606
|
+
* Filtering: `?types=audit,agent.created`
|
|
607
|
+
* Replay: `?since=2026-01-01T00:00:00Z` or Last-Event-ID header
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* ```typescript
|
|
611
|
+
* const stream = createEventStreamModule({ db, requireAuth: true });
|
|
612
|
+
*
|
|
613
|
+
* // In your request handler
|
|
614
|
+
* const response = stream.handleRequest(request);
|
|
615
|
+
* if (response) return response;
|
|
616
|
+
*
|
|
617
|
+
* // Emit from anywhere in your app
|
|
618
|
+
* stream.emit({
|
|
619
|
+
* id: crypto.generateId(),
|
|
620
|
+
* type: 'agent.created',
|
|
621
|
+
* timestamp: new Date(),
|
|
622
|
+
* data: { agentId: 'ag_123', name: 'my-agent' },
|
|
623
|
+
* });
|
|
624
|
+
* ```
|
|
625
|
+
*/
|
|
626
|
+
|
|
627
|
+
declare const EVENT_TYPES: readonly ["audit", "agent.created", "agent.revoked", "agent.rotated", "auth.signin", "auth.signout", "auth.failed", "delegation.created", "delegation.revoked", "budget.exceeded", "anomaly.detected", "cost.recorded"];
|
|
628
|
+
type EventType = (typeof EVENT_TYPES)[number];
|
|
629
|
+
interface StreamEvent {
|
|
630
|
+
id: string;
|
|
631
|
+
type: EventType;
|
|
632
|
+
timestamp: Date;
|
|
633
|
+
data: Record<string, unknown>;
|
|
634
|
+
agentId?: string;
|
|
635
|
+
userId?: string;
|
|
636
|
+
}
|
|
637
|
+
interface EventStreamConfig {
|
|
638
|
+
db: Database;
|
|
639
|
+
/** Maximum concurrent SSE connections (default: 100) */
|
|
640
|
+
maxConnections?: number;
|
|
641
|
+
/** Heartbeat interval in milliseconds (default: 30000) */
|
|
642
|
+
heartbeatIntervalMs?: number;
|
|
643
|
+
/** Restrict which event types this stream delivers (default: all) */
|
|
644
|
+
eventTypes?: EventType[];
|
|
645
|
+
/** Require a valid Bearer token to connect (default: true) */
|
|
646
|
+
requireAuth?: boolean;
|
|
647
|
+
/**
|
|
648
|
+
* Validate a Bearer token and return the subscriber ID (userId or agentId)
|
|
649
|
+
* on success, or null on failure.
|
|
650
|
+
*
|
|
651
|
+
* Only called when `requireAuth` is true. When omitted, any non-empty token
|
|
652
|
+
* is accepted and used as the subscriber ID.
|
|
653
|
+
*/
|
|
654
|
+
validateToken?: (token: string) => Promise<string | null>;
|
|
655
|
+
}
|
|
656
|
+
interface EventStreamModule {
|
|
657
|
+
/** Emit an event to all connected clients. */
|
|
658
|
+
emit(event: StreamEvent): void;
|
|
659
|
+
/** Handle an incoming HTTP request. Returns a Response or null when the request is not an SSE request. */
|
|
660
|
+
handleRequest(request: Request): Response | null;
|
|
661
|
+
/** Current number of active SSE connections. */
|
|
662
|
+
getConnectionCount(): number;
|
|
663
|
+
/** Replay persisted events since a timestamp, optionally filtered by type. */
|
|
664
|
+
replay(since: Date, types?: EventType[]): Promise<Result<StreamEvent[]>>;
|
|
665
|
+
/** Close all active connections and stop heartbeats. */
|
|
666
|
+
close(): void;
|
|
667
|
+
}
|
|
668
|
+
declare function createEventStreamModule(config: EventStreamConfig): EventStreamModule;
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Agent identity federation for TheAuth.
|
|
672
|
+
*
|
|
673
|
+
* Allows an agent created in one TheAuth instance (Service A) to
|
|
674
|
+
* authenticate at another TheAuth instance (Service B) without
|
|
675
|
+
* re-registration. The agent's identity, trust score, permissions,
|
|
676
|
+
* and delegation scope travel with the federation token.
|
|
677
|
+
*
|
|
678
|
+
* Federation tokens are short-lived JWTs signed by the source instance.
|
|
679
|
+
* The target instance verifies them by fetching the source's public key
|
|
680
|
+
* from `/.well-known/kavach-federation.json`. Optionally, a Verifiable
|
|
681
|
+
* Credential can be embedded for offline verification.
|
|
682
|
+
*
|
|
683
|
+
* @example
|
|
684
|
+
* ```typescript
|
|
685
|
+
* import { createFederationModule } from '@glinr/theauth/auth';
|
|
686
|
+
* import { generateKeyPair, exportJWK } from 'jose';
|
|
687
|
+
*
|
|
688
|
+
* const { publicKey, privateKey } = await generateKeyPair('EdDSA');
|
|
689
|
+
*
|
|
690
|
+
* const federation = createFederationModule({
|
|
691
|
+
* instanceId: 'instance-a',
|
|
692
|
+
* instanceUrl: 'https://a.example.com',
|
|
693
|
+
* signingKey: privateKey,
|
|
694
|
+
* });
|
|
695
|
+
*
|
|
696
|
+
* // Issue a token for an agent to carry to Service B
|
|
697
|
+
* const result = await federation.issueFederationToken('agent-123');
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
|
|
701
|
+
declare const TrustLevelSchema: z.ZodEnum<["full", "limited", "verify-only"]>;
|
|
702
|
+
type TrustLevel = z.infer<typeof TrustLevelSchema>;
|
|
703
|
+
interface TrustedInstance {
|
|
704
|
+
instanceId: string;
|
|
705
|
+
instanceUrl: string;
|
|
706
|
+
publicKey?: JsonWebKey;
|
|
707
|
+
trustLevel?: TrustLevel;
|
|
708
|
+
}
|
|
709
|
+
interface FederationConfig {
|
|
710
|
+
/** Unique identifier for this TheAuth instance */
|
|
711
|
+
instanceId: string;
|
|
712
|
+
/** Public URL of this instance */
|
|
713
|
+
instanceUrl: string;
|
|
714
|
+
/** EdDSA private key for signing federation tokens */
|
|
715
|
+
signingKey: CryptoKey;
|
|
716
|
+
/** Pre-configured trusted instances */
|
|
717
|
+
trustedInstances?: TrustedInstance[];
|
|
718
|
+
/** Trust any TheAuth instance (dev mode only) */
|
|
719
|
+
autoTrust?: boolean;
|
|
720
|
+
/** Federation token lifetime in seconds. Default: 300 (5 minutes). */
|
|
721
|
+
tokenTtlSeconds?: number;
|
|
722
|
+
}
|
|
723
|
+
interface FederationToken {
|
|
724
|
+
/** Signed JWT */
|
|
725
|
+
token: string;
|
|
726
|
+
/** Token expiration */
|
|
727
|
+
expiresAt: Date;
|
|
728
|
+
/** Agent this token was issued for */
|
|
729
|
+
agentId: string;
|
|
730
|
+
/** Source instance ID */
|
|
731
|
+
sourceInstance: string;
|
|
732
|
+
}
|
|
733
|
+
interface FederatedAgent {
|
|
734
|
+
/** Agent ID from the source instance */
|
|
735
|
+
agentId: string;
|
|
736
|
+
/** Instance that issued this agent's identity */
|
|
737
|
+
sourceInstance: string;
|
|
738
|
+
/** Source instance URL */
|
|
739
|
+
sourceInstanceUrl: string;
|
|
740
|
+
/** Permissions carried by the token */
|
|
741
|
+
permissions: string[];
|
|
742
|
+
/** Trust level (0-1) from the source instance */
|
|
743
|
+
trustScore: number;
|
|
744
|
+
/** Delegation scope, if any */
|
|
745
|
+
delegationScope: string[];
|
|
746
|
+
/** When this token was verified */
|
|
747
|
+
verifiedAt: Date;
|
|
748
|
+
/** Embedded Verifiable Credential JWT, if present */
|
|
749
|
+
credential?: string;
|
|
750
|
+
}
|
|
751
|
+
interface InstanceIdentity {
|
|
752
|
+
/** This instance's ID */
|
|
753
|
+
instanceId: string;
|
|
754
|
+
/** This instance's URL */
|
|
755
|
+
instanceUrl: string;
|
|
756
|
+
/** Public key in JWK format for verifying federation tokens */
|
|
757
|
+
publicKeyJwk: JsonWebKey;
|
|
758
|
+
/** Protocol version */
|
|
759
|
+
protocolVersion: string;
|
|
760
|
+
/** Supported features */
|
|
761
|
+
features: string[];
|
|
762
|
+
}
|
|
763
|
+
interface IssueFederationTokenInput {
|
|
764
|
+
/** Agent ID to issue the token for */
|
|
765
|
+
agentId: string;
|
|
766
|
+
/** Optional target instance ID (audience restriction) */
|
|
767
|
+
targetInstance?: string;
|
|
768
|
+
/** Agent permissions to include in the token */
|
|
769
|
+
permissions?: string[];
|
|
770
|
+
/** Agent trust score (0-1) */
|
|
771
|
+
trustScore?: number;
|
|
772
|
+
/** Delegation scope */
|
|
773
|
+
delegationScope?: string[];
|
|
774
|
+
/** Optional VC JWT to embed */
|
|
775
|
+
credential?: string;
|
|
776
|
+
}
|
|
777
|
+
/** The well-known document served at /.well-known/kavach-federation.json */
|
|
778
|
+
interface FederationWellKnown {
|
|
779
|
+
instanceId: string;
|
|
780
|
+
instanceUrl: string;
|
|
781
|
+
publicKeyJwk: JsonWebKey;
|
|
782
|
+
protocolVersion: string;
|
|
783
|
+
features: string[];
|
|
784
|
+
}
|
|
785
|
+
interface FederationModule {
|
|
786
|
+
/** Issue a federation token for an agent to use at another service */
|
|
787
|
+
issueFederationToken(input: IssueFederationTokenInput): Promise<Result<FederationToken>>;
|
|
788
|
+
/** Verify a federation token from another TheAuth instance */
|
|
789
|
+
verifyFederationToken(token: string): Promise<Result<FederatedAgent>>;
|
|
790
|
+
/** Get this instance's identity (for the well-known endpoint) */
|
|
791
|
+
getInstanceIdentity(): Promise<InstanceIdentity>;
|
|
792
|
+
/** Add a trusted instance */
|
|
793
|
+
addTrustedInstance(instance: TrustedInstance): Result<void>;
|
|
794
|
+
/** Remove a trusted instance by ID */
|
|
795
|
+
removeTrustedInstance(instanceId: string): Result<void>;
|
|
796
|
+
/** List all trusted instances */
|
|
797
|
+
listTrustedInstances(): TrustedInstance[];
|
|
798
|
+
/** Discover another instance via its well-known URL */
|
|
799
|
+
discoverInstance(url: string, fetchFn?: typeof globalThis.fetch): Promise<Result<TrustedInstance>>;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Create a federation module for cross-instance agent identity.
|
|
803
|
+
*
|
|
804
|
+
* The module signs short-lived JWTs that carry agent identity, permissions,
|
|
805
|
+
* and trust score. Remote instances verify these tokens using the source's
|
|
806
|
+
* public key, fetched from the well-known endpoint or pre-configured.
|
|
807
|
+
*/
|
|
808
|
+
declare function createFederationModule(config: FederationConfig): FederationModule;
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* GDPR module for TheAuth.
|
|
812
|
+
*
|
|
813
|
+
* Implements Article 17 (right to erasure) and Article 20 (right to data
|
|
814
|
+
* portability) for user accounts. Compliance-critical: every data removal
|
|
815
|
+
* path is explicit about which tables are affected.
|
|
816
|
+
*
|
|
817
|
+
* @example
|
|
818
|
+
* ```typescript
|
|
819
|
+
* const gdpr = createGdprModule(db);
|
|
820
|
+
*
|
|
821
|
+
* // Export all data for a user
|
|
822
|
+
* const export = await gdpr.exportUserData(userId);
|
|
823
|
+
*
|
|
824
|
+
* // Delete account, keeping anonymized audit trail
|
|
825
|
+
* const result = await gdpr.deleteUser(userId, { keepAuditLogs: true });
|
|
826
|
+
*
|
|
827
|
+
* // Anonymize PII but keep the account (e.g., for orgs that require it)
|
|
828
|
+
* await gdpr.anonymizeUser(userId);
|
|
829
|
+
* ```
|
|
830
|
+
*/
|
|
831
|
+
|
|
832
|
+
interface UserDataExport {
|
|
833
|
+
user: {
|
|
834
|
+
id: string;
|
|
835
|
+
email: string;
|
|
836
|
+
name: string | null;
|
|
837
|
+
createdAt: string;
|
|
838
|
+
};
|
|
839
|
+
agents: Array<{
|
|
840
|
+
id: string;
|
|
841
|
+
name: string;
|
|
842
|
+
type: string;
|
|
843
|
+
status: string;
|
|
844
|
+
createdAt: string;
|
|
845
|
+
}>;
|
|
846
|
+
sessions: Array<{
|
|
847
|
+
id: string;
|
|
848
|
+
createdAt: string;
|
|
849
|
+
expiresAt: string;
|
|
850
|
+
}>;
|
|
851
|
+
auditLogs: Array<{
|
|
852
|
+
action: string;
|
|
853
|
+
resource: string;
|
|
854
|
+
result: string;
|
|
855
|
+
timestamp: string;
|
|
856
|
+
}>;
|
|
857
|
+
delegations: Array<{
|
|
858
|
+
fromAgent: string;
|
|
859
|
+
toAgent: string;
|
|
860
|
+
createdAt: string;
|
|
861
|
+
}>;
|
|
862
|
+
organizations: Array<{
|
|
863
|
+
id: string;
|
|
864
|
+
name: string;
|
|
865
|
+
role: string;
|
|
866
|
+
}>;
|
|
867
|
+
apiKeys: Array<{
|
|
868
|
+
id: string;
|
|
869
|
+
name: string;
|
|
870
|
+
createdAt: string;
|
|
871
|
+
}>;
|
|
872
|
+
exportedAt: string;
|
|
873
|
+
}
|
|
874
|
+
interface DeleteOptions {
|
|
875
|
+
/**
|
|
876
|
+
* Keep anonymized audit logs (default: true).
|
|
877
|
+
*
|
|
878
|
+
* Required for most compliance frameworks — audit records must survive
|
|
879
|
+
* account deletion. User/agent identity is replaced with a stable hash so
|
|
880
|
+
* aggregate reporting remains consistent across deleted accounts.
|
|
881
|
+
*/
|
|
882
|
+
keepAuditLogs?: boolean;
|
|
883
|
+
/**
|
|
884
|
+
* Also delete organizations owned by this user (default: false).
|
|
885
|
+
*
|
|
886
|
+
* When false, ownership is left in place so other members are unaffected.
|
|
887
|
+
* Set to true only when the org has no other members or its data should
|
|
888
|
+
* also be erased.
|
|
889
|
+
*/
|
|
890
|
+
deleteOrganizations?: boolean;
|
|
891
|
+
}
|
|
892
|
+
interface DeleteResult {
|
|
893
|
+
deletedAgents: number;
|
|
894
|
+
deletedSessions: number;
|
|
895
|
+
deletedDelegations: number;
|
|
896
|
+
deletedApiKeys: number;
|
|
897
|
+
anonymizedAuditLogs: number;
|
|
898
|
+
}
|
|
899
|
+
interface GdprModule {
|
|
900
|
+
/** Export all user data as a structured JSON object (GDPR Article 20). */
|
|
901
|
+
exportUserData(userId: string): Promise<UserDataExport>;
|
|
902
|
+
/** Delete all user data (GDPR Article 17). Returns counts of removed records. */
|
|
903
|
+
deleteUser(userId: string, options?: DeleteOptions): Promise<DeleteResult>;
|
|
904
|
+
/**
|
|
905
|
+
* Anonymize user data instead of deleting.
|
|
906
|
+
*
|
|
907
|
+
* Replaces PII (email, name) with deterministic anonymous values while
|
|
908
|
+
* keeping the account structure intact. Useful when org membership or
|
|
909
|
+
* audit referential integrity must be preserved.
|
|
910
|
+
*/
|
|
911
|
+
anonymizeUser(userId: string): Promise<void>;
|
|
912
|
+
}
|
|
913
|
+
declare function createGdprModule(db: Database): GdprModule;
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* GDPR plugin for TheAuth.
|
|
917
|
+
*
|
|
918
|
+
* Exposes three self-service endpoints that authenticated users can call
|
|
919
|
+
* to exercise their data rights under GDPR Articles 17 and 20.
|
|
920
|
+
*
|
|
921
|
+
* Endpoints:
|
|
922
|
+
* GET /auth/gdpr/export – download a JSON export of all personal data
|
|
923
|
+
* DELETE /auth/gdpr/delete – permanently delete the account
|
|
924
|
+
* POST /auth/gdpr/anonymize – strip PII but keep the account shell
|
|
925
|
+
*
|
|
926
|
+
* @example
|
|
927
|
+
* ```typescript
|
|
928
|
+
* import { createKavach } from '@glinr/theauth';
|
|
929
|
+
* import { gdpr } from '@glinr/theauth/auth';
|
|
930
|
+
*
|
|
931
|
+
* const kavach = await createKavach({
|
|
932
|
+
* database: { provider: 'sqlite', url: 'kavach.db' },
|
|
933
|
+
* plugins: [gdpr()],
|
|
934
|
+
* });
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
|
|
938
|
+
declare function gdpr(): KavachPlugin;
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Have I Been Pwned password checking for TheAuth.
|
|
942
|
+
*
|
|
943
|
+
* Uses the k-anonymity model: only the first 5 hex characters of the SHA-1
|
|
944
|
+
* hash are sent to the API. The full hash (and the password itself) never
|
|
945
|
+
* leave the process.
|
|
946
|
+
*
|
|
947
|
+
* @see https://haveibeenpwned.com/API/v3#PwnedPasswords
|
|
948
|
+
*/
|
|
949
|
+
interface HibpConfig {
|
|
950
|
+
/** Reject passwords seen in more than N breaches (default: 0, reject any). */
|
|
951
|
+
threshold?: number;
|
|
952
|
+
/** Custom API base URL, e.g. for a self-hosted HIBP instance. */
|
|
953
|
+
apiUrl?: string;
|
|
954
|
+
/** Request timeout in milliseconds (default: 5000). */
|
|
955
|
+
timeoutMs?: number;
|
|
956
|
+
/**
|
|
957
|
+
* What to do when the HIBP API is unreachable or returns an error.
|
|
958
|
+
* - `'allow'` – treat the password as clean and let the user continue.
|
|
959
|
+
* - `'block'` – reject the password to be safe.
|
|
960
|
+
* Default: `'allow'`.
|
|
961
|
+
*/
|
|
962
|
+
onError?: "allow" | "block";
|
|
963
|
+
}
|
|
964
|
+
interface HibpModule {
|
|
965
|
+
/**
|
|
966
|
+
* Check whether the password appears in any known data breach.
|
|
967
|
+
* Returns the number of times it has been seen, or 0 if clean / API error
|
|
968
|
+
* with `onError: 'allow'`.
|
|
969
|
+
*/
|
|
970
|
+
check(password: string): Promise<number>;
|
|
971
|
+
/**
|
|
972
|
+
* Like `check`, but throws a `HibpBreachedError` when the breach count
|
|
973
|
+
* exceeds the configured threshold.
|
|
974
|
+
*/
|
|
975
|
+
enforce(password: string): Promise<void>;
|
|
976
|
+
}
|
|
977
|
+
declare class HibpBreachedError extends Error {
|
|
978
|
+
readonly count: number;
|
|
979
|
+
constructor(count: number);
|
|
980
|
+
}
|
|
981
|
+
declare function createHibpModule(config?: HibpConfig): HibpModule;
|
|
982
|
+
declare class HibpApiError extends Error {
|
|
983
|
+
constructor(message: string);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* JWT session plugin for TheAuth.
|
|
988
|
+
*
|
|
989
|
+
* Issues short-lived access JWTs and long-lived refresh tokens for general-purpose
|
|
990
|
+
* session management. This is distinct from the internal OIDC provider JWT — this
|
|
991
|
+
* plugin is for apps that want to vend their own session tokens without running a
|
|
992
|
+
* full OIDC flow.
|
|
993
|
+
*
|
|
994
|
+
* Access tokens are stateless JWTs (no DB lookup on verify). Refresh tokens are
|
|
995
|
+
* opaque random strings stored hashed in the database, soft-revoked on use.
|
|
996
|
+
*
|
|
997
|
+
* @example
|
|
998
|
+
* ```typescript
|
|
999
|
+
* const sessions = createJwtSessionModule({
|
|
1000
|
+
* secret: process.env.SESSION_SECRET,
|
|
1001
|
+
* issuer: 'https://myapp.com',
|
|
1002
|
+
* customClaims: (user) => ({ role: user.role, org: user.orgId }),
|
|
1003
|
+
* }, db);
|
|
1004
|
+
*
|
|
1005
|
+
* // On login
|
|
1006
|
+
* const result = await sessions.createSession({ id: user.id, email: user.email });
|
|
1007
|
+
* if (result.success) {
|
|
1008
|
+
* const { accessToken, refreshToken, expiresIn } = result.data;
|
|
1009
|
+
* }
|
|
1010
|
+
*
|
|
1011
|
+
* // On API request
|
|
1012
|
+
* const verified = await sessions.verifySession(bearerToken);
|
|
1013
|
+
* if (verified.success) {
|
|
1014
|
+
* const { userId, claims } = verified.data;
|
|
1015
|
+
* }
|
|
1016
|
+
*
|
|
1017
|
+
* // On token refresh
|
|
1018
|
+
* const refreshed = await sessions.refreshSession(refreshToken);
|
|
1019
|
+
*
|
|
1020
|
+
* // On logout
|
|
1021
|
+
* await sessions.revokeSession(refreshToken);
|
|
1022
|
+
* ```
|
|
1023
|
+
*/
|
|
1024
|
+
|
|
1025
|
+
interface JwtSessionConfig {
|
|
1026
|
+
/** Signing key. A plain string uses HMAC-SHA256 (must be >= 32 chars).
|
|
1027
|
+
* Pass a `CryptoKey` or `JsonWebKey` for RSA/EC algorithms. */
|
|
1028
|
+
secret: string | CryptoKey | JsonWebKey;
|
|
1029
|
+
/** JWT algorithm. Defaults to 'HS256' for string secrets, 'RS256' for keys. */
|
|
1030
|
+
algorithm?: string;
|
|
1031
|
+
/** Access token TTL in seconds. Default: 900 (15 min). */
|
|
1032
|
+
accessTokenTtl?: number;
|
|
1033
|
+
/** Refresh token TTL in seconds. Default: 604800 (7 days). */
|
|
1034
|
+
refreshTokenTtl?: number;
|
|
1035
|
+
/** JWT `iss` claim. */
|
|
1036
|
+
issuer?: string;
|
|
1037
|
+
/** JWT `aud` claim. */
|
|
1038
|
+
audience?: string;
|
|
1039
|
+
/** Attach extra claims to the access token payload. */
|
|
1040
|
+
customClaims?: (user: {
|
|
1041
|
+
id: string;
|
|
1042
|
+
email?: string;
|
|
1043
|
+
name?: string;
|
|
1044
|
+
}) => Record<string, unknown>;
|
|
1045
|
+
/**
|
|
1046
|
+
* Emit IETF agentic JWT claims on issued access tokens.
|
|
1047
|
+
*
|
|
1048
|
+
* When true, `agent_id`, `agent_type`, and `trust_tier` are included
|
|
1049
|
+
* in the token payload if the corresponding values are present on
|
|
1050
|
+
* `SessionUser.agenticContext`. Claims not present in the context are
|
|
1051
|
+
* omitted rather than fabricated. Off by default.
|
|
1052
|
+
*
|
|
1053
|
+
* @default false
|
|
1054
|
+
*/
|
|
1055
|
+
emitAgenticJwtClaims?: boolean;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Optional agentic context carried alongside a session user.
|
|
1059
|
+
*
|
|
1060
|
+
* Used when `JwtSessionConfig.emitAgenticJwtClaims` is true to populate
|
|
1061
|
+
* draft-goswami-agentic-jwt-00 claims on the issued access token.
|
|
1062
|
+
*/
|
|
1063
|
+
interface AgenticSessionContext {
|
|
1064
|
+
/** Stable agent identifier (populates `agent_id`). */
|
|
1065
|
+
agentId?: string;
|
|
1066
|
+
/** Operational mode (populates `agent_type`). */
|
|
1067
|
+
agentType?: AgentType;
|
|
1068
|
+
/** Trust tier band at issuance (populates `trust_tier`). */
|
|
1069
|
+
trustTier?: TrustTier;
|
|
1070
|
+
}
|
|
1071
|
+
interface SessionUser {
|
|
1072
|
+
id: string;
|
|
1073
|
+
email?: string;
|
|
1074
|
+
name?: string;
|
|
1075
|
+
image?: string;
|
|
1076
|
+
/**
|
|
1077
|
+
* Optional agentic context. Only used when
|
|
1078
|
+
* `JwtSessionConfig.emitAgenticJwtClaims` is true.
|
|
1079
|
+
*/
|
|
1080
|
+
agenticContext?: AgenticSessionContext;
|
|
1081
|
+
}
|
|
1082
|
+
interface SessionTokens {
|
|
1083
|
+
accessToken: string;
|
|
1084
|
+
refreshToken: string;
|
|
1085
|
+
/** Seconds until the access token expires (mirrors `accessTokenTtl`). */
|
|
1086
|
+
expiresIn: number;
|
|
1087
|
+
}
|
|
1088
|
+
interface VerifiedSession {
|
|
1089
|
+
userId: string;
|
|
1090
|
+
email?: string;
|
|
1091
|
+
name?: string;
|
|
1092
|
+
claims: Record<string, unknown>;
|
|
1093
|
+
}
|
|
1094
|
+
interface JwtSessionModule {
|
|
1095
|
+
/**
|
|
1096
|
+
* Issue a new access + refresh token pair for the given user.
|
|
1097
|
+
*
|
|
1098
|
+
* The refresh token is stored hashed. The raw value is returned once and
|
|
1099
|
+
* cannot be recovered from the database.
|
|
1100
|
+
*/
|
|
1101
|
+
createSession(user: SessionUser): Promise<Result<SessionTokens>>;
|
|
1102
|
+
/**
|
|
1103
|
+
* Verify an access token and return the embedded claims.
|
|
1104
|
+
*
|
|
1105
|
+
* Does not touch the database. Fails on expiry, wrong signature, or issuer/
|
|
1106
|
+
* audience mismatch.
|
|
1107
|
+
*/
|
|
1108
|
+
verifySession(token: string): Promise<Result<VerifiedSession>>;
|
|
1109
|
+
/**
|
|
1110
|
+
* Exchange a refresh token for a new access + refresh token pair.
|
|
1111
|
+
*
|
|
1112
|
+
* The incoming refresh token is soft-revoked (marked used) on success.
|
|
1113
|
+
* A brand-new refresh token is issued so that each refresh rotates the token.
|
|
1114
|
+
*/
|
|
1115
|
+
refreshSession(refreshToken: string): Promise<Result<SessionTokens>>;
|
|
1116
|
+
/**
|
|
1117
|
+
* Revoke a refresh token, preventing any further refreshes.
|
|
1118
|
+
*
|
|
1119
|
+
* Calling this on an already-revoked or unknown token is a no-op (returns
|
|
1120
|
+
* success) so that logout endpoints are idempotent.
|
|
1121
|
+
*/
|
|
1122
|
+
revokeSession(refreshToken: string): Promise<Result<void>>;
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Create a JWT session module backed by the provided database.
|
|
1126
|
+
*
|
|
1127
|
+
* The module is stateless beyond the database — multiple instances sharing
|
|
1128
|
+
* the same DB are safe and will honour each other's revocations.
|
|
1129
|
+
*/
|
|
1130
|
+
declare function createJwtSessionModule(config: JwtSessionConfig, db: Database): JwtSessionModule;
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Last login tracking module for TheAuth.
|
|
1134
|
+
*
|
|
1135
|
+
* Records each successful authentication event per user, capturing the method
|
|
1136
|
+
* used, optional IP address, optional user agent, and timestamp. A rolling
|
|
1137
|
+
* window of recent logins is kept — older entries beyond the configured limit
|
|
1138
|
+
* are pruned on every write so storage stays bounded.
|
|
1139
|
+
*
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```typescript
|
|
1142
|
+
* const loginHistory = createLastLoginModule({}, db);
|
|
1143
|
+
*
|
|
1144
|
+
* // After a successful sign-in
|
|
1145
|
+
* await loginHistory.recordLogin({
|
|
1146
|
+
* userId: 'usr_123',
|
|
1147
|
+
* method: 'magic-link',
|
|
1148
|
+
* ip: request.headers.get('x-forwarded-for') ?? undefined,
|
|
1149
|
+
* userAgent: request.headers.get('user-agent') ?? undefined,
|
|
1150
|
+
* });
|
|
1151
|
+
*
|
|
1152
|
+
* // Show the user their last login on a security page
|
|
1153
|
+
* const result = await loginHistory.getLastLogin('usr_123');
|
|
1154
|
+
* if (result.success) {
|
|
1155
|
+
* console.log(result.data?.method, result.data?.timestamp);
|
|
1156
|
+
* }
|
|
1157
|
+
* ```
|
|
1158
|
+
*/
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* All supported login methods.
|
|
1162
|
+
*
|
|
1163
|
+
* For OAuth providers use the `oauth:{provider}` pattern, e.g. `oauth:github`,
|
|
1164
|
+
* `oauth:google`, `oauth:microsoft`.
|
|
1165
|
+
*/
|
|
1166
|
+
type LoginMethod = "email-password" | "magic-link" | "email-otp" | "passkey" | `oauth:${string}` | "username-password" | "phone-sms" | "siwe" | "device-auth" | "anonymous" | "api-key";
|
|
1167
|
+
interface LastLoginConfig {
|
|
1168
|
+
/**
|
|
1169
|
+
* Maximum number of login events to retain per user.
|
|
1170
|
+
* Older rows beyond this limit are deleted on every `recordLogin` call.
|
|
1171
|
+
* Default: 10.
|
|
1172
|
+
*/
|
|
1173
|
+
maxHistoryPerUser?: number;
|
|
1174
|
+
}
|
|
1175
|
+
interface RecordLoginInput {
|
|
1176
|
+
userId: string;
|
|
1177
|
+
method: LoginMethod;
|
|
1178
|
+
/** Caller IP address. Stored as-is; normalise before passing if needed. */
|
|
1179
|
+
ip?: string;
|
|
1180
|
+
/** Raw value of the User-Agent request header. */
|
|
1181
|
+
userAgent?: string;
|
|
1182
|
+
}
|
|
1183
|
+
interface LoginEvent {
|
|
1184
|
+
id: string;
|
|
1185
|
+
userId: string;
|
|
1186
|
+
method: LoginMethod;
|
|
1187
|
+
ip: string | null;
|
|
1188
|
+
userAgent: string | null;
|
|
1189
|
+
timestamp: Date;
|
|
1190
|
+
}
|
|
1191
|
+
interface LastLoginModule {
|
|
1192
|
+
/**
|
|
1193
|
+
* Record a successful login event for a user.
|
|
1194
|
+
*
|
|
1195
|
+
* If the total stored events for that user exceed `maxHistoryPerUser`, the
|
|
1196
|
+
* oldest events are deleted so only the most recent N are kept.
|
|
1197
|
+
*/
|
|
1198
|
+
recordLogin(input: RecordLoginInput): Promise<Result<LoginEvent>>;
|
|
1199
|
+
/**
|
|
1200
|
+
* Retrieve the single most recent login event for a user.
|
|
1201
|
+
*
|
|
1202
|
+
* Returns `null` in `data` when no history exists for the user.
|
|
1203
|
+
*/
|
|
1204
|
+
getLastLogin(userId: string): Promise<Result<LoginEvent | null>>;
|
|
1205
|
+
/**
|
|
1206
|
+
* Return login history for a user, newest first.
|
|
1207
|
+
*
|
|
1208
|
+
* @param userId The user to look up.
|
|
1209
|
+
* @param limit Maximum number of events to return. Defaults to `maxHistoryPerUser`.
|
|
1210
|
+
*/
|
|
1211
|
+
getLoginHistory(userId: string, limit?: number): Promise<Result<LoginEvent[]>>;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Create a last-login tracking module backed by the provided database.
|
|
1215
|
+
*
|
|
1216
|
+
* The module is stateless — safe to instantiate multiple times against the
|
|
1217
|
+
* same database.
|
|
1218
|
+
*/
|
|
1219
|
+
declare function createLastLoginModule(config: LastLoginConfig, db: Database): LastLoginModule;
|
|
1220
|
+
|
|
1221
|
+
declare function magicLink(config: MagicLinkConfig): KavachPlugin;
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Types for the OAuth 2.0 / OIDC provider system.
|
|
1225
|
+
*
|
|
1226
|
+
* TheAuth uses PKCE (S256) for all provider flows regardless of whether
|
|
1227
|
+
* the provider strictly requires it. This prevents authorization code
|
|
1228
|
+
* interception attacks on both public and confidential clients.
|
|
1229
|
+
*/
|
|
1230
|
+
interface OAuthProviderConfig {
|
|
1231
|
+
/** OAuth application client ID. */
|
|
1232
|
+
clientId: string;
|
|
1233
|
+
/** OAuth application client secret. */
|
|
1234
|
+
clientSecret: string;
|
|
1235
|
+
/**
|
|
1236
|
+
* Additional scopes to request on top of the provider defaults.
|
|
1237
|
+
* The provider's default scopes are always included.
|
|
1238
|
+
*/
|
|
1239
|
+
scopes?: string[];
|
|
1240
|
+
/**
|
|
1241
|
+
* Override the redirect URI registered with the provider.
|
|
1242
|
+
* When omitted the module uses the `redirectUri` passed at call time.
|
|
1243
|
+
*/
|
|
1244
|
+
redirectUri?: string;
|
|
1245
|
+
}
|
|
1246
|
+
interface OAuthProvider {
|
|
1247
|
+
/** Machine-readable provider ID, e.g. `'google'`, `'github'`. */
|
|
1248
|
+
id: string;
|
|
1249
|
+
/** Human-readable provider name. */
|
|
1250
|
+
name: string;
|
|
1251
|
+
/** Base authorization endpoint URL. */
|
|
1252
|
+
authorizationUrl: string;
|
|
1253
|
+
/** Token exchange endpoint URL. */
|
|
1254
|
+
tokenUrl: string;
|
|
1255
|
+
/**
|
|
1256
|
+
* User profile endpoint URL.
|
|
1257
|
+
* Optional — some providers (e.g. Notion) embed user info in the token
|
|
1258
|
+
* response and have no separate endpoint.
|
|
1259
|
+
*/
|
|
1260
|
+
userInfoUrl: string | undefined;
|
|
1261
|
+
/** Effective scopes (defaults merged with any user-supplied extras). */
|
|
1262
|
+
scopes: string[];
|
|
1263
|
+
/**
|
|
1264
|
+
* Build the authorization redirect URL.
|
|
1265
|
+
*
|
|
1266
|
+
* @param state CSRF state value to be validated on callback.
|
|
1267
|
+
* @param codeVerifier PKCE code verifier — the provider derives the challenge.
|
|
1268
|
+
* @param redirectUri Callback URL to include in the authorization request.
|
|
1269
|
+
*/
|
|
1270
|
+
getAuthorizationUrl(state: string, codeVerifier: string, redirectUri: string): Promise<string>;
|
|
1271
|
+
/**
|
|
1272
|
+
* Exchange an authorization code for tokens.
|
|
1273
|
+
*
|
|
1274
|
+
* @param code The authorization code received on callback.
|
|
1275
|
+
* @param codeVerifier PKCE code verifier used to generate the original challenge.
|
|
1276
|
+
* @param redirectUri Must match the URI used in the authorization request.
|
|
1277
|
+
*/
|
|
1278
|
+
exchangeCode(code: string, codeVerifier: string, redirectUri: string): Promise<OAuthTokens>;
|
|
1279
|
+
/**
|
|
1280
|
+
* Fetch normalized user profile information from the provider.
|
|
1281
|
+
*
|
|
1282
|
+
* @param accessToken A valid access token issued by the provider.
|
|
1283
|
+
*/
|
|
1284
|
+
getUserInfo(accessToken: string): Promise<OAuthUserInfo>;
|
|
1285
|
+
}
|
|
1286
|
+
interface OAuthTokens {
|
|
1287
|
+
accessToken: string;
|
|
1288
|
+
refreshToken?: string;
|
|
1289
|
+
/** Lifetime of the access token in seconds. */
|
|
1290
|
+
expiresIn?: number;
|
|
1291
|
+
tokenType: string;
|
|
1292
|
+
/** Raw token response from the provider (unparsed claims). */
|
|
1293
|
+
raw: Record<string, unknown>;
|
|
1294
|
+
}
|
|
1295
|
+
interface OAuthUserInfo {
|
|
1296
|
+
/** Stable user ID at the provider. */
|
|
1297
|
+
id: string;
|
|
1298
|
+
/**
|
|
1299
|
+
* User email address.
|
|
1300
|
+
* Some providers (e.g. Reddit) do not expose email via OAuth; callers must
|
|
1301
|
+
* handle the undefined case, typically by requiring a separate email step.
|
|
1302
|
+
*/
|
|
1303
|
+
email: string | undefined;
|
|
1304
|
+
name?: string;
|
|
1305
|
+
/** URL to the user's avatar image. */
|
|
1306
|
+
avatar?: string;
|
|
1307
|
+
/** Full raw response from the provider's user info endpoint. */
|
|
1308
|
+
raw: Record<string, unknown>;
|
|
1309
|
+
}
|
|
1310
|
+
interface OAuthModuleConfig {
|
|
1311
|
+
/**
|
|
1312
|
+
* Map of provider ID to provider instance.
|
|
1313
|
+
*
|
|
1314
|
+
* @example
|
|
1315
|
+
* ```typescript
|
|
1316
|
+
* import { createGoogleProvider } from '@glinr/theauth/auth/oauth/providers/google';
|
|
1317
|
+
* import { createGithubProvider } from '@glinr/theauth/auth/oauth/providers/github';
|
|
1318
|
+
*
|
|
1319
|
+
* providers: {
|
|
1320
|
+
* google: createGoogleProvider({ clientId: '...', clientSecret: '...' }),
|
|
1321
|
+
* github: createGithubProvider({ clientId: '...', clientSecret: '...' }),
|
|
1322
|
+
* }
|
|
1323
|
+
* ```
|
|
1324
|
+
*/
|
|
1325
|
+
providers: Record<string, OAuthProvider>;
|
|
1326
|
+
/**
|
|
1327
|
+
* How long an OAuth state entry lives before it is considered expired.
|
|
1328
|
+
* Defaults to 600 seconds (10 minutes).
|
|
1329
|
+
*/
|
|
1330
|
+
stateTtlSeconds?: number;
|
|
1331
|
+
}
|
|
1332
|
+
interface OAuthModule {
|
|
1333
|
+
/**
|
|
1334
|
+
* Generate a PKCE-protected authorization URL and persist the state.
|
|
1335
|
+
*
|
|
1336
|
+
* Call this on your `/auth/:provider` route and redirect the user to the
|
|
1337
|
+
* returned URL.
|
|
1338
|
+
*
|
|
1339
|
+
* @returns `{ url, state }` — redirect to `url`; `state` is stored in the DB.
|
|
1340
|
+
*/
|
|
1341
|
+
getAuthorizationUrl(providerId: string, redirectUri: string): Promise<{
|
|
1342
|
+
url: string;
|
|
1343
|
+
state: string;
|
|
1344
|
+
}>;
|
|
1345
|
+
/**
|
|
1346
|
+
* Handle the provider callback and resolve (or create) a linked account.
|
|
1347
|
+
*
|
|
1348
|
+
* Call this on your `/auth/:provider/callback` route.
|
|
1349
|
+
*
|
|
1350
|
+
* @param providerId The provider that issued the callback.
|
|
1351
|
+
* @param code The authorization code from the query string.
|
|
1352
|
+
* @param state The state value from the query string (validated against DB).
|
|
1353
|
+
* @param redirectUri Must match the URI used in `getAuthorizationUrl`.
|
|
1354
|
+
* @returns The linked account row and normalized user info.
|
|
1355
|
+
*/
|
|
1356
|
+
handleCallback(providerId: string, code: string, state: string, redirectUri: string): Promise<OAuthCallbackResult>;
|
|
1357
|
+
/**
|
|
1358
|
+
* Manually link an OAuth provider account to an existing TheAuth user.
|
|
1359
|
+
*
|
|
1360
|
+
* Useful when you want to add a second provider to a user who already
|
|
1361
|
+
* authenticated via a different method.
|
|
1362
|
+
*/
|
|
1363
|
+
linkAccount(userId: string, providerId: string, userInfo: OAuthUserInfo, tokens: OAuthTokens): Promise<OAuthAccount>;
|
|
1364
|
+
/**
|
|
1365
|
+
* Look up the TheAuth user linked to a given provider account.
|
|
1366
|
+
*
|
|
1367
|
+
* Returns `null` when no link exists yet.
|
|
1368
|
+
*/
|
|
1369
|
+
findLinkedUser(providerId: string, providerAccountId: string): Promise<{
|
|
1370
|
+
userId: string;
|
|
1371
|
+
} | null>;
|
|
1372
|
+
}
|
|
1373
|
+
interface OAuthCallbackResult {
|
|
1374
|
+
/** Whether this callback created a new linked account (vs. found an existing one). */
|
|
1375
|
+
isNewAccount: boolean;
|
|
1376
|
+
account: OAuthAccount;
|
|
1377
|
+
userInfo: OAuthUserInfo;
|
|
1378
|
+
tokens: OAuthTokens;
|
|
1379
|
+
}
|
|
1380
|
+
interface OAuthAccount {
|
|
1381
|
+
id: string;
|
|
1382
|
+
userId: string;
|
|
1383
|
+
provider: string;
|
|
1384
|
+
providerAccountId: string;
|
|
1385
|
+
accessToken: string;
|
|
1386
|
+
refreshToken: string | null;
|
|
1387
|
+
expiresAt: Date | null;
|
|
1388
|
+
createdAt: Date;
|
|
1389
|
+
updatedAt: Date;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* OAuth module factory.
|
|
1394
|
+
*
|
|
1395
|
+
* Provides three operations on top of any configured set of providers:
|
|
1396
|
+
*
|
|
1397
|
+
* 1. `getAuthorizationUrl` — generate a PKCE-protected authorization URL and
|
|
1398
|
+
* persist the state + code verifier to the database.
|
|
1399
|
+
* 2. `handleCallback` — validate state, exchange the code, fetch user info,
|
|
1400
|
+
* and create or return an existing linked account.
|
|
1401
|
+
* 3. `linkAccount` — manually link provider tokens to an existing user.
|
|
1402
|
+
* 4. `findLinkedUser` — look up which user owns a provider account.
|
|
1403
|
+
*
|
|
1404
|
+
* All state operations use the `kavach_oauth_states` table; all account links
|
|
1405
|
+
* use the `kavach_oauth_accounts` table — both defined in `./schema.ts`.
|
|
1406
|
+
*/
|
|
1407
|
+
|
|
1408
|
+
/**
|
|
1409
|
+
* Create an OAuth module bound to a database instance.
|
|
1410
|
+
*
|
|
1411
|
+
* @example
|
|
1412
|
+
* ```typescript
|
|
1413
|
+
* import { createOAuthModule } from '@glinr/theauth/auth/oauth';
|
|
1414
|
+
* import { createGoogleProvider } from '@glinr/theauth/auth/oauth/providers/google';
|
|
1415
|
+
*
|
|
1416
|
+
* const oauth = createOAuthModule(db, {
|
|
1417
|
+
* providers: {
|
|
1418
|
+
* google: createGoogleProvider({ clientId: '...', clientSecret: '...' }),
|
|
1419
|
+
* },
|
|
1420
|
+
* });
|
|
1421
|
+
*
|
|
1422
|
+
* // On /auth/google
|
|
1423
|
+
* const { url } = await oauth.getAuthorizationUrl('google', 'https://my.app/callback');
|
|
1424
|
+
* return redirect(url);
|
|
1425
|
+
*
|
|
1426
|
+
* // On /auth/google/callback
|
|
1427
|
+
* const { account, userInfo } = await oauth.handleCallback(
|
|
1428
|
+
* 'google', req.query.code, req.query.state, 'https://my.app/callback',
|
|
1429
|
+
* );
|
|
1430
|
+
* ```
|
|
1431
|
+
*/
|
|
1432
|
+
declare function createOAuthModule(db: Database, config: OAuthModuleConfig): OAuthModule;
|
|
1433
|
+
|
|
1434
|
+
interface OAuthPluginConfig extends OAuthModuleConfig {
|
|
1435
|
+
/**
|
|
1436
|
+
* Build the redirect URI for a given provider.
|
|
1437
|
+
*
|
|
1438
|
+
* When omitted the plugin constructs the URI from `ctx.config.baseUrl`
|
|
1439
|
+
* using the pattern `{baseUrl}/auth/oauth/callback/{provider}`.
|
|
1440
|
+
*/
|
|
1441
|
+
buildRedirectUri?: (provider: string, baseUrl: string) => string;
|
|
1442
|
+
}
|
|
1443
|
+
declare function oauth(config: OAuthPluginConfig): KavachPlugin;
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* Apple Sign In provider (OAuth 2.0 / OIDC).
|
|
1447
|
+
*
|
|
1448
|
+
* Endpoints:
|
|
1449
|
+
* - Authorization: https://appleid.apple.com/auth/authorize
|
|
1450
|
+
* - Token: https://appleid.apple.com/auth/token
|
|
1451
|
+
*
|
|
1452
|
+
* Notes:
|
|
1453
|
+
* - Apple does not expose a dedicated UserInfo endpoint. Instead, it embeds
|
|
1454
|
+
* user claims (email, name) in the `id_token` JWT on first authorization.
|
|
1455
|
+
* On subsequent authorizations the `user` form-post field is absent — only
|
|
1456
|
+
* the `id_token` carries the `sub` and `email` claims.
|
|
1457
|
+
* - PKCE S256 is supported and recommended.
|
|
1458
|
+
* - `response_mode` must be `form_post` when requesting the `name` scope,
|
|
1459
|
+
* because Apple POSTs back a `user` JSON field alongside the code.
|
|
1460
|
+
* - The `name` scope only delivers user data on the *first* authorization.
|
|
1461
|
+
* Store it immediately; subsequent logins will not include it.
|
|
1462
|
+
*
|
|
1463
|
+
* Docs: https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms
|
|
1464
|
+
*/
|
|
1465
|
+
|
|
1466
|
+
/**
|
|
1467
|
+
* Create an Apple Sign In provider instance.
|
|
1468
|
+
*
|
|
1469
|
+
* Apple requires a JWT client secret (signed with your private key) rather
|
|
1470
|
+
* than a static secret. Generate the client secret JWT before passing it in
|
|
1471
|
+
* as `clientSecret`.
|
|
1472
|
+
*
|
|
1473
|
+
* @example
|
|
1474
|
+
* ```typescript
|
|
1475
|
+
* const apple = createAppleProvider({
|
|
1476
|
+
* clientId: process.env.APPLE_CLIENT_ID, // Services ID, e.g. com.example.app
|
|
1477
|
+
* clientSecret: appleClientSecretJwt, // ES256 JWT generated from private key
|
|
1478
|
+
* });
|
|
1479
|
+
* ```
|
|
1480
|
+
*/
|
|
1481
|
+
declare function createAppleProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Atlassian OAuth 2.0 (3LO) provider.
|
|
1485
|
+
*
|
|
1486
|
+
* Endpoints:
|
|
1487
|
+
* - Authorization: https://auth.atlassian.com/authorize
|
|
1488
|
+
* - Token: https://auth.atlassian.com/oauth/token
|
|
1489
|
+
* - UserInfo: https://api.atlassian.com/me
|
|
1490
|
+
*
|
|
1491
|
+
* Notes:
|
|
1492
|
+
* - PKCE S256 is supported by Atlassian's OAuth 2.0 implementation.
|
|
1493
|
+
* - The `audience` parameter (`api.atlassian.com`) is required on the
|
|
1494
|
+
* authorization URL. Without it, tokens will not be accepted by the
|
|
1495
|
+
* Atlassian APIs.
|
|
1496
|
+
* - The `read:me` scope grants access to the user's identity (account ID,
|
|
1497
|
+
* email, name, avatar). Add `offline_access` if refresh tokens are needed.
|
|
1498
|
+
* - Atlassian account IDs are in the format `557058:xxxxxxxx-xxxx-...`.
|
|
1499
|
+
*
|
|
1500
|
+
* Docs: https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/
|
|
1501
|
+
*/
|
|
1502
|
+
|
|
1503
|
+
declare const DEFAULT_ATLASSIAN_SCOPES: string[];
|
|
1504
|
+
declare function normalizeProfile$9(raw: Record<string, unknown>): OAuthUserInfo;
|
|
1505
|
+
/**
|
|
1506
|
+
* Create an Atlassian OAuth provider instance.
|
|
1507
|
+
*
|
|
1508
|
+
* @example
|
|
1509
|
+
* ```typescript
|
|
1510
|
+
* const atlassian = createAtlassianProvider({
|
|
1511
|
+
* clientId: process.env.ATLASSIAN_CLIENT_ID,
|
|
1512
|
+
* clientSecret: process.env.ATLASSIAN_CLIENT_SECRET,
|
|
1513
|
+
* });
|
|
1514
|
+
* ```
|
|
1515
|
+
*/
|
|
1516
|
+
declare function createAtlassianProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Discord OAuth 2.0 provider.
|
|
1520
|
+
*
|
|
1521
|
+
* Endpoints:
|
|
1522
|
+
* - Authorization: https://discord.com/api/oauth2/authorize
|
|
1523
|
+
* - Token: https://discord.com/api/oauth2/token
|
|
1524
|
+
* - UserInfo: https://discord.com/api/users/@me
|
|
1525
|
+
*
|
|
1526
|
+
* Notes:
|
|
1527
|
+
* - PKCE S256 is supported as of the Discord OAuth 2.0 implementation.
|
|
1528
|
+
* - The `identify` scope grants access to the user object (ID, username,
|
|
1529
|
+
* avatar). The `email` scope is required separately to receive the email.
|
|
1530
|
+
* - Avatar URLs are constructed from the user ID and avatar hash. When the
|
|
1531
|
+
* user has no custom avatar, `avatar` is null and Discord serves a default
|
|
1532
|
+
* based on the discriminator (or username for the new username system).
|
|
1533
|
+
* - Discord user IDs are Snowflakes (64-bit integers serialized as strings).
|
|
1534
|
+
*
|
|
1535
|
+
* Docs: https://discord.com/developers/docs/topics/oauth2
|
|
1536
|
+
*/
|
|
1537
|
+
|
|
1538
|
+
declare const DEFAULT_DISCORD_SCOPES: string[];
|
|
1539
|
+
/**
|
|
1540
|
+
* Create a Discord OAuth provider instance.
|
|
1541
|
+
*
|
|
1542
|
+
* @example
|
|
1543
|
+
* ```typescript
|
|
1544
|
+
* const discord = createDiscordProvider({
|
|
1545
|
+
* clientId: process.env.DISCORD_CLIENT_ID,
|
|
1546
|
+
* clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
|
1547
|
+
* });
|
|
1548
|
+
* ```
|
|
1549
|
+
*/
|
|
1550
|
+
declare function createDiscordProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1551
|
+
declare function normalizeProfile$8(raw: Record<string, unknown>): OAuthUserInfo;
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* Dropbox OAuth 2.0 provider.
|
|
1555
|
+
*
|
|
1556
|
+
* Endpoints:
|
|
1557
|
+
* - Authorization: https://www.dropbox.com/oauth2/authorize
|
|
1558
|
+
* - Token: https://api.dropboxapi.com/oauth2/token
|
|
1559
|
+
* - UserInfo: https://api.dropboxapi.com/2/users/get_current_account (POST)
|
|
1560
|
+
*
|
|
1561
|
+
* Notes:
|
|
1562
|
+
* - PKCE S256 is supported by Dropbox's OAuth 2.0 implementation (since 2021).
|
|
1563
|
+
* - The userinfo endpoint is a POST with an empty body (JSON null is the
|
|
1564
|
+
* documented request body). No query params are needed.
|
|
1565
|
+
* - The `account_info.read` scope grants access to basic account info including
|
|
1566
|
+
* email, name, and account ID.
|
|
1567
|
+
* - Dropbox account IDs start with "dbid:" and are stable across sessions.
|
|
1568
|
+
* - The `name` object contains `display_name`, `given_name`, `surname`, etc.
|
|
1569
|
+
*
|
|
1570
|
+
* Docs: https://developers.dropbox.com/oauth-guide
|
|
1571
|
+
*/
|
|
1572
|
+
|
|
1573
|
+
declare const DEFAULT_DROPBOX_SCOPES: string[];
|
|
1574
|
+
declare function normalizeProfile$7(raw: Record<string, unknown>): OAuthUserInfo;
|
|
1575
|
+
/**
|
|
1576
|
+
* Create a Dropbox OAuth provider instance.
|
|
1577
|
+
*
|
|
1578
|
+
* @example
|
|
1579
|
+
* ```typescript
|
|
1580
|
+
* const dropbox = createDropboxProvider({
|
|
1581
|
+
* clientId: process.env.DROPBOX_CLIENT_ID,
|
|
1582
|
+
* clientSecret: process.env.DROPBOX_CLIENT_SECRET,
|
|
1583
|
+
* });
|
|
1584
|
+
* ```
|
|
1585
|
+
*/
|
|
1586
|
+
declare function createDropboxProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Figma OAuth 2.0 provider.
|
|
1590
|
+
*
|
|
1591
|
+
* Endpoints:
|
|
1592
|
+
* - Authorization: https://www.figma.com/oauth
|
|
1593
|
+
* - Token: https://api.figma.com/v1/oauth/token
|
|
1594
|
+
* - UserInfo: https://api.figma.com/v1/me
|
|
1595
|
+
*
|
|
1596
|
+
* Notes:
|
|
1597
|
+
* - PKCE S256 is supported by Figma's OAuth implementation.
|
|
1598
|
+
* - The `file_read` scope is the minimum required for sign-in; it grants
|
|
1599
|
+
* read access to files, projects, and user information.
|
|
1600
|
+
* - The email address is always returned; Figma accounts always have one.
|
|
1601
|
+
*
|
|
1602
|
+
* Docs: https://www.figma.com/developers/api#authentication
|
|
1603
|
+
*/
|
|
1604
|
+
|
|
1605
|
+
declare const DEFAULT_FIGMA_SCOPES: string[];
|
|
1606
|
+
declare function normalizeProfile$6(raw: Record<string, unknown>): OAuthUserInfo;
|
|
1607
|
+
/**
|
|
1608
|
+
* Create a Figma OAuth provider instance.
|
|
1609
|
+
*
|
|
1610
|
+
* @example
|
|
1611
|
+
* ```typescript
|
|
1612
|
+
* const figma = createFigmaProvider({
|
|
1613
|
+
* clientId: process.env.FIGMA_CLIENT_ID,
|
|
1614
|
+
* clientSecret: process.env.FIGMA_CLIENT_SECRET,
|
|
1615
|
+
* });
|
|
1616
|
+
* ```
|
|
1617
|
+
*/
|
|
1618
|
+
declare function createFigmaProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* Generic OIDC provider factory.
|
|
1622
|
+
*
|
|
1623
|
+
* Builds a fully functional OAuthProvider from a minimal config. When an
|
|
1624
|
+
* OIDC issuer URL is supplied the factory constructs the standard
|
|
1625
|
+
* `/.well-known/openid-configuration` discovery URL. Explicit endpoint
|
|
1626
|
+
* overrides take precedence over discovery, so the factory works with
|
|
1627
|
+
* providers that do not implement RFC 8414.
|
|
1628
|
+
*
|
|
1629
|
+
* Spec references:
|
|
1630
|
+
* - OIDC Discovery: https://openid.net/specs/openid-connect-discovery-1_0.html
|
|
1631
|
+
* - RFC 8414 (OAuth 2.0 Authorization Server Metadata)
|
|
1632
|
+
*/
|
|
1633
|
+
|
|
1634
|
+
interface GenericOIDCConfig {
|
|
1635
|
+
/** Machine-readable provider ID, e.g. `'okta'`, `'auth0'`. */
|
|
1636
|
+
id: string;
|
|
1637
|
+
/** Human-readable display name, e.g. `'Okta'`. */
|
|
1638
|
+
name: string;
|
|
1639
|
+
/**
|
|
1640
|
+
* OIDC issuer URL. Used to derive the discovery document URL as
|
|
1641
|
+
* `${issuer}/.well-known/openid-configuration` when explicit endpoint
|
|
1642
|
+
* overrides are not provided.
|
|
1643
|
+
*
|
|
1644
|
+
* @example "https://dev-12345678.okta.com"
|
|
1645
|
+
*/
|
|
1646
|
+
issuer: string;
|
|
1647
|
+
/** OAuth application client ID. */
|
|
1648
|
+
clientId: string;
|
|
1649
|
+
/** OAuth application client secret. */
|
|
1650
|
+
clientSecret: string;
|
|
1651
|
+
/**
|
|
1652
|
+
* Scopes to request. Defaults to `['openid', 'email', 'profile']`.
|
|
1653
|
+
*/
|
|
1654
|
+
scopes?: string[];
|
|
1655
|
+
/**
|
|
1656
|
+
* Override the redirect URI registered with the provider.
|
|
1657
|
+
* When omitted the URI passed at call time is used.
|
|
1658
|
+
*/
|
|
1659
|
+
redirectUri?: string;
|
|
1660
|
+
/** Authorization endpoint. Overrides discovery. */
|
|
1661
|
+
authorizationUrl?: string;
|
|
1662
|
+
/** Token endpoint. Overrides discovery. */
|
|
1663
|
+
tokenUrl?: string;
|
|
1664
|
+
/** UserInfo endpoint. Overrides discovery. */
|
|
1665
|
+
userinfoUrl?: string;
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Create an OAuthProvider backed by a standard OIDC issuer.
|
|
1669
|
+
*
|
|
1670
|
+
* Endpoints are resolved from the issuer's discovery document on first use
|
|
1671
|
+
* and cached in memory for the lifetime of the process. Pass explicit
|
|
1672
|
+
* `authorizationUrl`, `tokenUrl`, and `userinfoUrl` to bypass discovery.
|
|
1673
|
+
*
|
|
1674
|
+
* @example
|
|
1675
|
+
* ```typescript
|
|
1676
|
+
* const okta = genericOIDC({
|
|
1677
|
+
* id: "okta",
|
|
1678
|
+
* name: "Okta",
|
|
1679
|
+
* issuer: "https://dev-12345678.okta.com",
|
|
1680
|
+
* clientId: process.env.OKTA_CLIENT_ID,
|
|
1681
|
+
* clientSecret: process.env.OKTA_CLIENT_SECRET,
|
|
1682
|
+
* });
|
|
1683
|
+
* ```
|
|
1684
|
+
*/
|
|
1685
|
+
declare function genericOIDC(config: GenericOIDCConfig): OAuthProvider;
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* GitHub OAuth 2.0 provider.
|
|
1689
|
+
*
|
|
1690
|
+
* Endpoints:
|
|
1691
|
+
* - Authorization: https://github.com/login/oauth/authorize
|
|
1692
|
+
* - Token: https://github.com/login/oauth/access_token
|
|
1693
|
+
* - UserInfo: https://api.github.com/user
|
|
1694
|
+
* - Emails: https://api.github.com/user/emails (for primary verified email)
|
|
1695
|
+
*
|
|
1696
|
+
* Notes:
|
|
1697
|
+
* - GitHub does not natively support PKCE in its OAuth flow, but we send the
|
|
1698
|
+
* `code_challenge` parameter anyway — it is silently ignored, which is safe.
|
|
1699
|
+
* The code verifier is still validated server-side within TheAuth state
|
|
1700
|
+
* storage so the CSRF protection guarantee holds.
|
|
1701
|
+
* - The `user:email` scope is required to read the primary email when it is
|
|
1702
|
+
* set to private on the GitHub profile.
|
|
1703
|
+
* - GitHub tokens do not carry an `expires_in` field for classic tokens.
|
|
1704
|
+
*
|
|
1705
|
+
* Docs: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps
|
|
1706
|
+
*/
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* Create a GitHub OAuth provider instance.
|
|
1710
|
+
*
|
|
1711
|
+
* @example
|
|
1712
|
+
* ```typescript
|
|
1713
|
+
* const github = createGithubProvider({
|
|
1714
|
+
* clientId: process.env.GITHUB_CLIENT_ID,
|
|
1715
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
1716
|
+
* });
|
|
1717
|
+
* ```
|
|
1718
|
+
*/
|
|
1719
|
+
declare function createGithubProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* GitLab OAuth 2.0 provider.
|
|
1723
|
+
*
|
|
1724
|
+
* Endpoints (gitlab.com — self-managed instances replace the base URL):
|
|
1725
|
+
* - Authorization: https://gitlab.com/oauth/authorize
|
|
1726
|
+
* - Token: https://gitlab.com/oauth/token
|
|
1727
|
+
* - UserInfo: https://gitlab.com/api/v4/user
|
|
1728
|
+
*
|
|
1729
|
+
* Notes:
|
|
1730
|
+
* - PKCE S256 is supported.
|
|
1731
|
+
* - The `read_user` scope grants access to the `/api/v4/user` endpoint which
|
|
1732
|
+
* returns the authenticated user's profile including email.
|
|
1733
|
+
* - GitLab user IDs are integers; they are converted to strings for the
|
|
1734
|
+
* TheAuth normalized format.
|
|
1735
|
+
* - For self-managed GitLab instances, override the URLs by providing a
|
|
1736
|
+
* custom base URL and constructing the provider accordingly.
|
|
1737
|
+
*
|
|
1738
|
+
* Docs: https://docs.gitlab.com/ee/api/oauth2.html
|
|
1739
|
+
*/
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* Create a GitLab OAuth provider instance.
|
|
1743
|
+
*
|
|
1744
|
+
* @example
|
|
1745
|
+
* ```typescript
|
|
1746
|
+
* const gitlab = createGitlabProvider({
|
|
1747
|
+
* clientId: process.env.GITLAB_CLIENT_ID,
|
|
1748
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET,
|
|
1749
|
+
* });
|
|
1750
|
+
* ```
|
|
1751
|
+
*/
|
|
1752
|
+
declare function createGitlabProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* Google OAuth 2.0 / OIDC provider.
|
|
1756
|
+
*
|
|
1757
|
+
* Endpoints:
|
|
1758
|
+
* - Authorization: https://accounts.google.com/o/oauth2/v2/auth
|
|
1759
|
+
* - Token: https://oauth2.googleapis.com/token
|
|
1760
|
+
* - UserInfo: https://www.googleapis.com/oauth2/v3/userinfo
|
|
1761
|
+
*
|
|
1762
|
+
* PKCE S256 is always enforced. Google supports it for all client types
|
|
1763
|
+
* and the spec mandates it for public clients.
|
|
1764
|
+
*
|
|
1765
|
+
* Docs: https://developers.google.com/identity/protocols/oauth2
|
|
1766
|
+
*/
|
|
1767
|
+
|
|
1768
|
+
/**
|
|
1769
|
+
* Create a Google OAuth provider instance.
|
|
1770
|
+
*
|
|
1771
|
+
* @example
|
|
1772
|
+
* ```typescript
|
|
1773
|
+
* const google = createGoogleProvider({
|
|
1774
|
+
* clientId: process.env.GOOGLE_CLIENT_ID,
|
|
1775
|
+
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
1776
|
+
* });
|
|
1777
|
+
* ```
|
|
1778
|
+
*/
|
|
1779
|
+
declare function createGoogleProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1780
|
+
|
|
1781
|
+
/**
|
|
1782
|
+
* LinkedIn OAuth 2.0 / OIDC provider.
|
|
1783
|
+
*
|
|
1784
|
+
* Endpoints:
|
|
1785
|
+
* - Authorization: https://www.linkedin.com/oauth/v2/authorization
|
|
1786
|
+
* - Token: https://www.linkedin.com/oauth/v2/accessToken
|
|
1787
|
+
* - UserInfo: https://api.linkedin.com/v2/userinfo (OIDC endpoint)
|
|
1788
|
+
*
|
|
1789
|
+
* Notes:
|
|
1790
|
+
* - LinkedIn's OIDC userinfo endpoint (`/v2/userinfo`) is available when the
|
|
1791
|
+
* `openid` scope is requested. It returns standard OIDC claims including
|
|
1792
|
+
* `sub`, `name`, `email`, and `picture`.
|
|
1793
|
+
* - PKCE is not supported by LinkedIn's OAuth server. The code challenge is
|
|
1794
|
+
* sent for symmetry but silently ignored. CSRF protection via `state` still
|
|
1795
|
+
* applies within TheAuth.
|
|
1796
|
+
* - The `sub` claim is the LinkedIn member ID and is stable across sessions.
|
|
1797
|
+
*
|
|
1798
|
+
* Docs: https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2
|
|
1799
|
+
*/
|
|
1800
|
+
|
|
1801
|
+
/**
|
|
1802
|
+
* Create a LinkedIn OAuth provider instance.
|
|
1803
|
+
*
|
|
1804
|
+
* @example
|
|
1805
|
+
* ```typescript
|
|
1806
|
+
* const linkedin = createLinkedInProvider({
|
|
1807
|
+
* clientId: process.env.LINKEDIN_CLIENT_ID,
|
|
1808
|
+
* clientSecret: process.env.LINKEDIN_CLIENT_SECRET,
|
|
1809
|
+
* });
|
|
1810
|
+
* ```
|
|
1811
|
+
*/
|
|
1812
|
+
declare function createLinkedInProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* Microsoft identity platform (Entra ID / Azure AD) OAuth 2.0 / OIDC provider.
|
|
1816
|
+
*
|
|
1817
|
+
* Endpoints (common tenant — works for personal accounts and work/school):
|
|
1818
|
+
* - Authorization: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
|
|
1819
|
+
* - Token: https://login.microsoftonline.com/common/oauth2/v2.0/token
|
|
1820
|
+
* - UserInfo: https://graph.microsoft.com/v1.0/me
|
|
1821
|
+
*
|
|
1822
|
+
* Notes:
|
|
1823
|
+
* - PKCE S256 is supported and required for public clients.
|
|
1824
|
+
* - `User.Read` is a Microsoft Graph permission, not a standard OIDC scope.
|
|
1825
|
+
* It grants access to the `/me` endpoint.
|
|
1826
|
+
* - The `id` field on the Graph `/me` response is the Entra object ID, which
|
|
1827
|
+
* is stable across tenant changes and is safe to use as a primary key.
|
|
1828
|
+
* - For single-tenant apps, replace `common` with the tenant ID or domain.
|
|
1829
|
+
*
|
|
1830
|
+
* Docs: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
|
|
1831
|
+
*/
|
|
1832
|
+
|
|
1833
|
+
/**
|
|
1834
|
+
* Create a Microsoft identity platform OAuth provider instance.
|
|
1835
|
+
*
|
|
1836
|
+
* @example
|
|
1837
|
+
* ```typescript
|
|
1838
|
+
* const microsoft = createMicrosoftProvider({
|
|
1839
|
+
* clientId: process.env.MICROSOFT_CLIENT_ID,
|
|
1840
|
+
* clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
|
|
1841
|
+
* });
|
|
1842
|
+
* ```
|
|
1843
|
+
*/
|
|
1844
|
+
declare function createMicrosoftProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
* Notion OAuth 2.0 provider.
|
|
1848
|
+
*
|
|
1849
|
+
* Endpoints:
|
|
1850
|
+
* - Authorization: https://api.notion.com/v1/oauth/authorize
|
|
1851
|
+
* - Token: https://api.notion.com/v1/oauth/token
|
|
1852
|
+
* - UserInfo: embedded in the token response (`owner` field)
|
|
1853
|
+
*
|
|
1854
|
+
* Notes:
|
|
1855
|
+
* - Notion does not have a separate UserInfo endpoint. User identity is
|
|
1856
|
+
* returned as part of the token exchange response inside `owner.user`.
|
|
1857
|
+
* The provider captures the token response in a closure so that
|
|
1858
|
+
* `getUserInfo` can extract it without a redundant network call.
|
|
1859
|
+
* - The token endpoint uses HTTP Basic auth (client_id:client_secret).
|
|
1860
|
+
* - All Notion API requests require the `Notion-Version` header.
|
|
1861
|
+
* - Notion uses integration-level permissions rather than OAuth scopes.
|
|
1862
|
+
* Workspaces a user authorizes appear in `workspace_id` / `workspace_name`
|
|
1863
|
+
* in the token response.
|
|
1864
|
+
* - The `owner.user.person.email` field is present only when the integration
|
|
1865
|
+
* is authorized by a person (not a bot). For bot authorizations
|
|
1866
|
+
* `owner.type` is `"workspace"` and `email` may be absent.
|
|
1867
|
+
* - PKCE is not documented by Notion; the code_challenge is omitted for
|
|
1868
|
+
* compatibility with their authorization server.
|
|
1869
|
+
*
|
|
1870
|
+
* Docs: https://developers.notion.com/docs/authorization
|
|
1871
|
+
*/
|
|
1872
|
+
|
|
1873
|
+
declare const DEFAULT_NOTION_SCOPES: string[];
|
|
1874
|
+
declare function normalizeProfile$5(raw: Record<string, unknown>): OAuthUserInfo;
|
|
1875
|
+
/**
|
|
1876
|
+
* Create a Notion OAuth provider instance.
|
|
1877
|
+
*
|
|
1878
|
+
* @example
|
|
1879
|
+
* ```typescript
|
|
1880
|
+
* const notion = createNotionProvider({
|
|
1881
|
+
* clientId: process.env.NOTION_CLIENT_ID,
|
|
1882
|
+
* clientSecret: process.env.NOTION_CLIENT_SECRET,
|
|
1883
|
+
* });
|
|
1884
|
+
* ```
|
|
1885
|
+
*/
|
|
1886
|
+
declare function createNotionProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
1887
|
+
|
|
1888
|
+
/**
|
|
1889
|
+
* Preset OAuth provider configs.
|
|
1890
|
+
*
|
|
1891
|
+
* Each export is a factory function that takes `(clientId, clientSecret)`
|
|
1892
|
+
* and returns a config accepted by `genericOIDC` or usable directly as a
|
|
1893
|
+
* plain provider when the provider does not support OIDC discovery.
|
|
1894
|
+
*
|
|
1895
|
+
* OIDC-capable providers (Auth0, Okta) use `genericOIDC` and require the
|
|
1896
|
+
* caller to supply their tenant/domain as a third argument.
|
|
1897
|
+
*
|
|
1898
|
+
* All other presets return a `GenericOIDCConfig`-compatible object with
|
|
1899
|
+
* explicit endpoints so they work without any network discovery call.
|
|
1900
|
+
*/
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* Facebook (Meta) OAuth 2.0.
|
|
1904
|
+
*
|
|
1905
|
+
* Docs: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow
|
|
1906
|
+
*/
|
|
1907
|
+
declare function facebookProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1908
|
+
/**
|
|
1909
|
+
* Spotify OAuth 2.0.
|
|
1910
|
+
*
|
|
1911
|
+
* Docs: https://developer.spotify.com/documentation/web-api/concepts/authorization
|
|
1912
|
+
*/
|
|
1913
|
+
declare function spotifyProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1914
|
+
/**
|
|
1915
|
+
* Twitch OAuth 2.0 / OIDC.
|
|
1916
|
+
*
|
|
1917
|
+
* Docs: https://dev.twitch.tv/docs/authentication
|
|
1918
|
+
*/
|
|
1919
|
+
declare function twitchProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1920
|
+
/**
|
|
1921
|
+
* Reddit OAuth 2.0.
|
|
1922
|
+
*
|
|
1923
|
+
* Docs: https://github.com/reddit-archive/reddit/wiki/OAuth2
|
|
1924
|
+
*/
|
|
1925
|
+
declare function redditProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1926
|
+
/**
|
|
1927
|
+
* Dropbox OAuth 2.0.
|
|
1928
|
+
*
|
|
1929
|
+
* Docs: https://developers.dropbox.com/oauth-guide
|
|
1930
|
+
*/
|
|
1931
|
+
declare function dropboxProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1932
|
+
/**
|
|
1933
|
+
* Zoom OAuth 2.0 / OIDC.
|
|
1934
|
+
*
|
|
1935
|
+
* Docs: https://developers.zoom.us/docs/integrations/oauth/
|
|
1936
|
+
*/
|
|
1937
|
+
declare function zoomProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1938
|
+
/**
|
|
1939
|
+
* Notion OAuth 2.0.
|
|
1940
|
+
*
|
|
1941
|
+
* Docs: https://developers.notion.com/docs/authorization
|
|
1942
|
+
*/
|
|
1943
|
+
declare function notionProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1944
|
+
/**
|
|
1945
|
+
* Figma OAuth 2.0.
|
|
1946
|
+
*
|
|
1947
|
+
* Docs: https://www.figma.com/developers/api#authentication
|
|
1948
|
+
*/
|
|
1949
|
+
declare function figmaProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1950
|
+
/**
|
|
1951
|
+
* Bitbucket OAuth 2.0.
|
|
1952
|
+
*
|
|
1953
|
+
* Docs: https://developer.atlassian.com/cloud/bitbucket/oauth-2/
|
|
1954
|
+
*/
|
|
1955
|
+
declare function bitbucketProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1956
|
+
/**
|
|
1957
|
+
* Atlassian OAuth 2.0 (Jira, Confluence, etc.).
|
|
1958
|
+
*
|
|
1959
|
+
* Docs: https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/
|
|
1960
|
+
*/
|
|
1961
|
+
declare function atlassianProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1962
|
+
/**
|
|
1963
|
+
* Yahoo OAuth 2.0 / OIDC.
|
|
1964
|
+
*
|
|
1965
|
+
* Docs: https://developer.yahoo.com/oauth2/guide/
|
|
1966
|
+
*/
|
|
1967
|
+
declare function yahooProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1968
|
+
/**
|
|
1969
|
+
* LINE Login OAuth 2.0 / OIDC.
|
|
1970
|
+
*
|
|
1971
|
+
* Docs: https://developers.line.biz/en/docs/line-login/integrate-line-login/
|
|
1972
|
+
*/
|
|
1973
|
+
declare function lineProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1974
|
+
/**
|
|
1975
|
+
* Coinbase OAuth 2.0.
|
|
1976
|
+
*
|
|
1977
|
+
* Docs: https://docs.cdp.coinbase.com/coinbase-app/docs/coinbase-connect-reference
|
|
1978
|
+
*/
|
|
1979
|
+
declare function coinbaseProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1980
|
+
/**
|
|
1981
|
+
* TikTok OAuth 2.0.
|
|
1982
|
+
*
|
|
1983
|
+
* Docs: https://developers.tiktok.com/doc/oauth-user-access-token-management
|
|
1984
|
+
*/
|
|
1985
|
+
declare function tiktokProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1986
|
+
/**
|
|
1987
|
+
* PayPal OAuth 2.0 / OIDC.
|
|
1988
|
+
*
|
|
1989
|
+
* Docs: https://developer.paypal.com/api/rest/authentication/
|
|
1990
|
+
*/
|
|
1991
|
+
declare function paypalProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1992
|
+
/**
|
|
1993
|
+
* Salesforce OAuth 2.0 / OIDC.
|
|
1994
|
+
*
|
|
1995
|
+
* Docs: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_flows.htm
|
|
1996
|
+
*/
|
|
1997
|
+
declare function salesforceProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
1998
|
+
/**
|
|
1999
|
+
* VK ID OAuth 2.0.
|
|
2000
|
+
*
|
|
2001
|
+
* Docs: https://id.vk.com/about/business/go/docs/ru/vkid/latest/vkid/sdk/web/get-started
|
|
2002
|
+
*/
|
|
2003
|
+
declare function vkProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2004
|
+
/**
|
|
2005
|
+
* Kakao OAuth 2.0.
|
|
2006
|
+
*
|
|
2007
|
+
* Docs: https://developers.kakao.com/docs/latest/en/kakaologin/rest-api
|
|
2008
|
+
*/
|
|
2009
|
+
declare function kakaoProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2010
|
+
/**
|
|
2011
|
+
* Naver OAuth 2.0.
|
|
2012
|
+
*
|
|
2013
|
+
* Docs: https://developers.naver.com/docs/login/api/api.md
|
|
2014
|
+
*/
|
|
2015
|
+
declare function naverProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2016
|
+
/**
|
|
2017
|
+
* Hugging Face OAuth 2.0 / OIDC.
|
|
2018
|
+
*
|
|
2019
|
+
* Docs: https://huggingface.co/docs/hub/en/oauth
|
|
2020
|
+
*/
|
|
2021
|
+
declare function huggingfaceProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2022
|
+
/**
|
|
2023
|
+
* Roblox OAuth 2.0 / OIDC.
|
|
2024
|
+
*
|
|
2025
|
+
* Docs: https://create.roblox.com/docs/cloud/open-cloud/oauth2-overview
|
|
2026
|
+
*/
|
|
2027
|
+
declare function robloxProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2028
|
+
/**
|
|
2029
|
+
* Vercel OAuth 2.0.
|
|
2030
|
+
*
|
|
2031
|
+
* Docs: https://vercel.com/docs/integrations/create-integration/submit-integration#oauth2
|
|
2032
|
+
*/
|
|
2033
|
+
declare function vercelProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2034
|
+
/**
|
|
2035
|
+
* Linear OAuth 2.0.
|
|
2036
|
+
*
|
|
2037
|
+
* Docs: https://developers.linear.app/docs/oauth/authentication
|
|
2038
|
+
*/
|
|
2039
|
+
declare function linearProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2040
|
+
/**
|
|
2041
|
+
* Railway OAuth 2.0.
|
|
2042
|
+
*
|
|
2043
|
+
* Docs: https://docs.railway.app/reference/public-api#oauth2
|
|
2044
|
+
*/
|
|
2045
|
+
declare function railwayProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2046
|
+
/**
|
|
2047
|
+
* Kick OAuth 2.0.
|
|
2048
|
+
*
|
|
2049
|
+
* Docs: https://docs.kick.com/getting-started/authorization-oauth2-flow
|
|
2050
|
+
*/
|
|
2051
|
+
declare function kickProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2052
|
+
/**
|
|
2053
|
+
* WeChat OAuth 2.0 (Web Login via QR code).
|
|
2054
|
+
*
|
|
2055
|
+
* Docs: https://developers.weixin.qq.com/doc/oplatform/en/Website_App/WeChat_Login/Wechat_Login.html
|
|
2056
|
+
*/
|
|
2057
|
+
declare function wechatProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2058
|
+
/**
|
|
2059
|
+
* Polar OAuth 2.0 / OIDC.
|
|
2060
|
+
*
|
|
2061
|
+
* Docs: https://docs.polar.sh/api-reference/oauth2
|
|
2062
|
+
*/
|
|
2063
|
+
declare function polarProvider(clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2064
|
+
/**
|
|
2065
|
+
* Auth0 OIDC provider.
|
|
2066
|
+
*
|
|
2067
|
+
* Requires the Auth0 tenant domain (e.g. `"dev-abc123.us.auth0.com"`).
|
|
2068
|
+
*
|
|
2069
|
+
* Docs: https://auth0.com/docs/authenticate/protocols/openid-connect-protocol
|
|
2070
|
+
*
|
|
2071
|
+
* @example
|
|
2072
|
+
* ```typescript
|
|
2073
|
+
* const auth0 = auth0Provider("dev-abc123.us.auth0.com", clientId, clientSecret);
|
|
2074
|
+
* ```
|
|
2075
|
+
*/
|
|
2076
|
+
declare function auth0Provider(domain: string, clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2077
|
+
/**
|
|
2078
|
+
* Okta OIDC provider.
|
|
2079
|
+
*
|
|
2080
|
+
* Requires the Okta domain (e.g. `"dev-12345678.okta.com"`).
|
|
2081
|
+
*
|
|
2082
|
+
* Docs: https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/
|
|
2083
|
+
*
|
|
2084
|
+
* @example
|
|
2085
|
+
* ```typescript
|
|
2086
|
+
* const okta = oktaProvider("dev-12345678.okta.com", clientId, clientSecret);
|
|
2087
|
+
* ```
|
|
2088
|
+
*/
|
|
2089
|
+
declare function oktaProvider(domain: string, clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2090
|
+
/**
|
|
2091
|
+
* AWS Cognito OIDC provider.
|
|
2092
|
+
*
|
|
2093
|
+
* Requires the Cognito hosted UI domain (e.g. `"my-app.auth.us-east-1.amazoncognito.com"`).
|
|
2094
|
+
*
|
|
2095
|
+
* Docs: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html
|
|
2096
|
+
*
|
|
2097
|
+
* @example
|
|
2098
|
+
* ```typescript
|
|
2099
|
+
* const cognito = cognitoProvider(
|
|
2100
|
+
* "my-app.auth.us-east-1.amazoncognito.com",
|
|
2101
|
+
* clientId,
|
|
2102
|
+
* clientSecret,
|
|
2103
|
+
* );
|
|
2104
|
+
* ```
|
|
2105
|
+
*/
|
|
2106
|
+
declare function cognitoProvider(domain: string, clientId: string, clientSecret: string, scopes?: string[]): OAuthProvider;
|
|
2107
|
+
|
|
2108
|
+
/**
|
|
2109
|
+
* Reddit OAuth 2.0 provider.
|
|
2110
|
+
*
|
|
2111
|
+
* Endpoints:
|
|
2112
|
+
* - Authorization: https://www.reddit.com/api/v1/authorize
|
|
2113
|
+
* - Token: https://www.reddit.com/api/v1/access_token
|
|
2114
|
+
* - UserInfo: https://oauth.reddit.com/api/v1/me
|
|
2115
|
+
*
|
|
2116
|
+
* Notes:
|
|
2117
|
+
* - Reddit's token endpoint uses HTTP Basic authentication (client_id as the
|
|
2118
|
+
* username, client_secret as the password) rather than posting credentials
|
|
2119
|
+
* in the request body.
|
|
2120
|
+
* - The `identity` scope grants access to the user's Reddit account info.
|
|
2121
|
+
* - Reddit does not expose the user's email address via OAuth; the `name`
|
|
2122
|
+
* field (Reddit username) is the stable identifier.
|
|
2123
|
+
* - The UserInfo endpoint requires a descriptive `User-Agent` header. Reddit
|
|
2124
|
+
* blocks requests with generic agents (e.g., "python-requests"). Format:
|
|
2125
|
+
* `platform:app_id:version (by /u/username)`.
|
|
2126
|
+
* - Avatar URLs (`icon_img`) include query parameters; strip them when storing
|
|
2127
|
+
* to avoid caching issues.
|
|
2128
|
+
* - PKCE is supported but Reddit also accepts flows without it for server-side
|
|
2129
|
+
* apps; TheAuth uses PKCE S256 consistently.
|
|
2130
|
+
*
|
|
2131
|
+
* Docs: https://www.reddit.com/dev/api/oauth
|
|
2132
|
+
*/
|
|
2133
|
+
|
|
2134
|
+
declare const DEFAULT_REDDIT_SCOPES: string[];
|
|
2135
|
+
/**
|
|
2136
|
+
* Create a Reddit OAuth provider instance.
|
|
2137
|
+
*
|
|
2138
|
+
* @example
|
|
2139
|
+
* ```typescript
|
|
2140
|
+
* const reddit = createRedditProvider({
|
|
2141
|
+
* clientId: process.env.REDDIT_CLIENT_ID,
|
|
2142
|
+
* clientSecret: process.env.REDDIT_CLIENT_SECRET,
|
|
2143
|
+
* });
|
|
2144
|
+
* ```
|
|
2145
|
+
*/
|
|
2146
|
+
declare function createRedditProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
2147
|
+
declare function normalizeProfile$4(raw: Record<string, unknown>): OAuthUserInfo;
|
|
2148
|
+
|
|
2149
|
+
/**
|
|
2150
|
+
* Slack OAuth 2.0 / OIDC provider.
|
|
2151
|
+
*
|
|
2152
|
+
* Endpoints:
|
|
2153
|
+
* - Authorization: https://slack.com/oauth/v2/authorize
|
|
2154
|
+
* - Token: https://slack.com/api/oauth.v2.access
|
|
2155
|
+
* - UserInfo: https://slack.com/api/openid.connect.userInfo
|
|
2156
|
+
*
|
|
2157
|
+
* Notes:
|
|
2158
|
+
* - Slack's v2 OAuth uses OpenID Connect for user sign-in. The
|
|
2159
|
+
* `openid.connect.userInfo` endpoint returns OIDC-standard claims.
|
|
2160
|
+
* - The token exchange response has a nested structure: `authed_user.access_token`
|
|
2161
|
+
* for the user token and a bot token at the top level when bot scopes are
|
|
2162
|
+
* included. For sign-in we want the user token.
|
|
2163
|
+
* - PKCE is not natively supported by Slack's OAuth v2 server. The code
|
|
2164
|
+
* challenge is sent but silently ignored — CSRF protection via `state` still
|
|
2165
|
+
* applies within TheAuth.
|
|
2166
|
+
* - Slack user IDs are workspace-scoped, not global. The `sub` claim from the
|
|
2167
|
+
* OIDC userinfo endpoint is the globally unique identifier across workspaces.
|
|
2168
|
+
*
|
|
2169
|
+
* Docs: https://api.slack.com/authentication/sign-in-with-slack
|
|
2170
|
+
*/
|
|
2171
|
+
|
|
2172
|
+
declare const DEFAULT_SLACK_SCOPES: string[];
|
|
2173
|
+
/**
|
|
2174
|
+
* Create a Slack OAuth provider instance.
|
|
2175
|
+
*
|
|
2176
|
+
* @example
|
|
2177
|
+
* ```typescript
|
|
2178
|
+
* const slack = createSlackProvider({
|
|
2179
|
+
* clientId: process.env.SLACK_CLIENT_ID,
|
|
2180
|
+
* clientSecret: process.env.SLACK_CLIENT_SECRET,
|
|
2181
|
+
* });
|
|
2182
|
+
* ```
|
|
2183
|
+
*/
|
|
2184
|
+
declare function createSlackProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
2185
|
+
declare function normalizeProfile$3(raw: Record<string, unknown>): OAuthUserInfo;
|
|
2186
|
+
|
|
2187
|
+
/**
|
|
2188
|
+
* Spotify OAuth 2.0 provider.
|
|
2189
|
+
*
|
|
2190
|
+
* Endpoints:
|
|
2191
|
+
* - Authorization: https://accounts.spotify.com/authorize
|
|
2192
|
+
* - Token: https://accounts.spotify.com/api/token
|
|
2193
|
+
* - UserInfo: https://api.spotify.com/v1/me
|
|
2194
|
+
*
|
|
2195
|
+
* Notes:
|
|
2196
|
+
* - PKCE S256 is supported and encouraged for public clients.
|
|
2197
|
+
* - The `user-read-email` scope is required to get the user's email.
|
|
2198
|
+
* - The `user-read-private` scope is required to access the user's country
|
|
2199
|
+
* and subscription type. Both are included in the defaults for sign-in.
|
|
2200
|
+
* - Email may be absent from the response when the account was created without
|
|
2201
|
+
* one (e.g., via Facebook sign-up on Spotify). Handle the undefined case.
|
|
2202
|
+
* - Avatar images are returned as an array of `images`; the first entry is
|
|
2203
|
+
* typically the largest.
|
|
2204
|
+
*
|
|
2205
|
+
* Docs: https://developer.spotify.com/documentation/web-api/concepts/authorization
|
|
2206
|
+
*/
|
|
2207
|
+
|
|
2208
|
+
declare const DEFAULT_SPOTIFY_SCOPES: string[];
|
|
2209
|
+
declare function normalizeProfile$2(raw: Record<string, unknown>): OAuthUserInfo;
|
|
2210
|
+
/**
|
|
2211
|
+
* Create a Spotify OAuth provider instance.
|
|
2212
|
+
*
|
|
2213
|
+
* @example
|
|
2214
|
+
* ```typescript
|
|
2215
|
+
* const spotify = createSpotifyProvider({
|
|
2216
|
+
* clientId: process.env.SPOTIFY_CLIENT_ID,
|
|
2217
|
+
* clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
|
|
2218
|
+
* });
|
|
2219
|
+
* ```
|
|
2220
|
+
*/
|
|
2221
|
+
declare function createSpotifyProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
2222
|
+
|
|
2223
|
+
/**
|
|
2224
|
+
* Twitch OAuth 2.0 provider.
|
|
2225
|
+
*
|
|
2226
|
+
* Endpoints:
|
|
2227
|
+
* - Authorization: https://id.twitch.tv/oauth2/authorize
|
|
2228
|
+
* - Token: https://id.twitch.tv/oauth2/token
|
|
2229
|
+
* - UserInfo: https://api.twitch.tv/helix/users
|
|
2230
|
+
*
|
|
2231
|
+
* Notes:
|
|
2232
|
+
* - PKCE S256 is supported by the Twitch OAuth 2.0 implementation.
|
|
2233
|
+
* - The `user:read:email` scope is required to receive the user's email address.
|
|
2234
|
+
* - The UserInfo endpoint (/helix/users) requires a `Client-ID` header in
|
|
2235
|
+
* addition to the Bearer token. Without it the request returns 400.
|
|
2236
|
+
* - User data is nested under a `data` array; the authenticated user is always
|
|
2237
|
+
* the first element.
|
|
2238
|
+
* - Profile image URLs are direct CDN links and may change when the user
|
|
2239
|
+
* updates their profile picture.
|
|
2240
|
+
*
|
|
2241
|
+
* Docs: https://dev.twitch.tv/docs/authentication/
|
|
2242
|
+
*/
|
|
2243
|
+
|
|
2244
|
+
declare const DEFAULT_TWITCH_SCOPES: string[];
|
|
2245
|
+
/**
|
|
2246
|
+
* Create a Twitch OAuth provider instance.
|
|
2247
|
+
*
|
|
2248
|
+
* @example
|
|
2249
|
+
* ```typescript
|
|
2250
|
+
* const twitch = createTwitchProvider({
|
|
2251
|
+
* clientId: process.env.TWITCH_CLIENT_ID,
|
|
2252
|
+
* clientSecret: process.env.TWITCH_CLIENT_SECRET,
|
|
2253
|
+
* });
|
|
2254
|
+
* ```
|
|
2255
|
+
*/
|
|
2256
|
+
declare function createTwitchProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
2257
|
+
declare function normalizeProfile$1(raw: Record<string, unknown>): OAuthUserInfo;
|
|
2258
|
+
|
|
2259
|
+
/**
|
|
2260
|
+
* Twitter / X OAuth 2.0 provider.
|
|
2261
|
+
*
|
|
2262
|
+
* Endpoints:
|
|
2263
|
+
* - Authorization: https://twitter.com/i/oauth2/authorize
|
|
2264
|
+
* - Token: https://api.twitter.com/2/oauth2/token
|
|
2265
|
+
* - UserInfo: https://api.twitter.com/2/users/me
|
|
2266
|
+
*
|
|
2267
|
+
* Notes:
|
|
2268
|
+
* - Twitter OAuth 2.0 (the v2 API) mandates PKCE S256 for all public clients.
|
|
2269
|
+
* Confidential clients may omit PKCE but it is always safer to include it.
|
|
2270
|
+
* - The token exchange requires HTTP Basic auth (`client_id:client_secret`)
|
|
2271
|
+
* rather than including credentials in the request body.
|
|
2272
|
+
* - The `/2/users/me` endpoint returns a minimal set of fields by default.
|
|
2273
|
+
* Additional fields (profile_image_url, name) must be requested via the
|
|
2274
|
+
* `user.fields` query parameter.
|
|
2275
|
+
* - Twitter does not return an email address through the standard OAuth 2.0
|
|
2276
|
+
* flow. Email access requires a separate elevated API access application
|
|
2277
|
+
* and the `tweet.read` scope alone does not grant it.
|
|
2278
|
+
* - User IDs are numeric strings and stable across username changes.
|
|
2279
|
+
*
|
|
2280
|
+
* Docs: https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code
|
|
2281
|
+
*/
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* Create a Twitter / X OAuth provider instance.
|
|
2285
|
+
*
|
|
2286
|
+
* Twitter requires PKCE for all flows. The `clientSecret` is still needed for
|
|
2287
|
+
* the token exchange (sent as HTTP Basic auth).
|
|
2288
|
+
*
|
|
2289
|
+
* @example
|
|
2290
|
+
* ```typescript
|
|
2291
|
+
* const twitter = createTwitterProvider({
|
|
2292
|
+
* clientId: process.env.TWITTER_CLIENT_ID,
|
|
2293
|
+
* clientSecret: process.env.TWITTER_CLIENT_SECRET,
|
|
2294
|
+
* });
|
|
2295
|
+
* ```
|
|
2296
|
+
*/
|
|
2297
|
+
declare function createTwitterProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
2298
|
+
|
|
2299
|
+
/**
|
|
2300
|
+
* Zoom OAuth 2.0 provider.
|
|
2301
|
+
*
|
|
2302
|
+
* Endpoints:
|
|
2303
|
+
* - Authorization: https://zoom.us/oauth/authorize
|
|
2304
|
+
* - Token: https://zoom.us/oauth/token
|
|
2305
|
+
* - UserInfo: https://api.zoom.us/v2/users/me
|
|
2306
|
+
*
|
|
2307
|
+
* Notes:
|
|
2308
|
+
* - PKCE S256 is supported by Zoom's OAuth implementation.
|
|
2309
|
+
* - The `user:read` scope grants read access to the authenticated user's
|
|
2310
|
+
* account details including email, name, and profile picture.
|
|
2311
|
+
* - Zoom user IDs are alphanumeric strings, not numeric.
|
|
2312
|
+
* - The `pic_url` field may be absent when the user has not set a profile photo.
|
|
2313
|
+
*
|
|
2314
|
+
* Docs: https://developers.zoom.us/docs/integrations/oauth/
|
|
2315
|
+
*/
|
|
2316
|
+
|
|
2317
|
+
declare const DEFAULT_ZOOM_SCOPES: string[];
|
|
2318
|
+
declare function normalizeProfile(raw: Record<string, unknown>): OAuthUserInfo;
|
|
2319
|
+
/**
|
|
2320
|
+
* Create a Zoom OAuth provider instance.
|
|
2321
|
+
*
|
|
2322
|
+
* @example
|
|
2323
|
+
* ```typescript
|
|
2324
|
+
* const zoom = createZoomProvider({
|
|
2325
|
+
* clientId: process.env.ZOOM_CLIENT_ID,
|
|
2326
|
+
* clientSecret: process.env.ZOOM_CLIENT_SECRET,
|
|
2327
|
+
* });
|
|
2328
|
+
* ```
|
|
2329
|
+
*/
|
|
2330
|
+
declare function createZoomProvider(config: OAuthProviderConfig): OAuthProvider;
|
|
2331
|
+
|
|
2332
|
+
/**
|
|
2333
|
+
* OAuth proxy module for mobile apps.
|
|
2334
|
+
*
|
|
2335
|
+
* Mobile apps cannot safely store OAuth client secrets. This module acts as a
|
|
2336
|
+
* server-side intermediary: the mobile app redirects to the provider via
|
|
2337
|
+
* TheAuth, which holds the secret and exchanges the authorization code for
|
|
2338
|
+
* tokens on the app's behalf.
|
|
2339
|
+
*
|
|
2340
|
+
* Flow:
|
|
2341
|
+
* 1. Mobile app calls GET /auth/oauth-proxy/start?provider=google&redirect_uri=myapp://callback
|
|
2342
|
+
* 2. TheAuth validates redirect_uri, stores proxy state, returns provider auth URL.
|
|
2343
|
+
* 3. User authenticates with the provider in a browser.
|
|
2344
|
+
* 4. Provider redirects to TheAuth callback with code + state.
|
|
2345
|
+
* 5. TheAuth exchanges the code (using the server-held client secret), then
|
|
2346
|
+
* redirects the mobile app to its custom scheme URL with tokens as query params.
|
|
2347
|
+
*
|
|
2348
|
+
* Security:
|
|
2349
|
+
* - redirect_uri is validated against an explicit allowlist — no open redirects.
|
|
2350
|
+
* - Proxy state is a random UUID stored in memory with a 10-minute TTL.
|
|
2351
|
+
* - PKCE passthrough: the mobile app may supply a code_challenge; TheAuth
|
|
2352
|
+
* forwards it to the provider and passes the verifier back via the callback.
|
|
2353
|
+
*/
|
|
2354
|
+
|
|
2355
|
+
interface OAuthProxyConfig {
|
|
2356
|
+
/**
|
|
2357
|
+
* Allowed redirect URIs for mobile apps (e.g. `"myapp://callback"`).
|
|
2358
|
+
* Only exact matches or scheme-prefix matches (when entry ends with `://`)
|
|
2359
|
+
* are allowed. No wildcards. This is an allowlist — anything not listed
|
|
2360
|
+
* will be rejected.
|
|
2361
|
+
*/
|
|
2362
|
+
allowedRedirectUris: string[];
|
|
2363
|
+
/** Rate limit per IP. Defaults to 20 requests per 60 seconds. */
|
|
2364
|
+
rateLimit?: {
|
|
2365
|
+
max: number;
|
|
2366
|
+
windowSeconds: number;
|
|
2367
|
+
};
|
|
2368
|
+
/**
|
|
2369
|
+
* How long a proxy state entry lives in seconds.
|
|
2370
|
+
* Defaults to 600 (10 minutes).
|
|
2371
|
+
*/
|
|
2372
|
+
stateTtlSeconds?: number;
|
|
2373
|
+
}
|
|
2374
|
+
interface ProxyTokens {
|
|
2375
|
+
accessToken: string;
|
|
2376
|
+
refreshToken?: string;
|
|
2377
|
+
idToken?: string;
|
|
2378
|
+
expiresIn?: number;
|
|
2379
|
+
}
|
|
2380
|
+
interface OAuthProxyModule {
|
|
2381
|
+
/**
|
|
2382
|
+
* Start the proxy flow.
|
|
2383
|
+
*
|
|
2384
|
+
* Validates `redirectUri` against the allowlist, generates a PKCE verifier,
|
|
2385
|
+
* stores proxy state keyed by an opaque `proxyState` value, and returns the
|
|
2386
|
+
* provider authorization URL for the caller to redirect to.
|
|
2387
|
+
*
|
|
2388
|
+
* @param provider Provider ID (must be in `providers` map).
|
|
2389
|
+
* @param redirectUri Mobile app callback URI. Must be in `allowedRedirectUris`.
|
|
2390
|
+
* @param state Optional caller-supplied state passed back on completion.
|
|
2391
|
+
* @param codeChallenge Optional PKCE code challenge from the mobile app (S256).
|
|
2392
|
+
*/
|
|
2393
|
+
startFlow(provider: string, redirectUri: string, state?: string, codeChallenge?: string): Promise<{
|
|
2394
|
+
authUrl: string;
|
|
2395
|
+
proxyState: string;
|
|
2396
|
+
}>;
|
|
2397
|
+
/**
|
|
2398
|
+
* Handle the provider callback.
|
|
2399
|
+
*
|
|
2400
|
+
* Looks up the stored proxy state, exchanges the code with the provider,
|
|
2401
|
+
* and returns the final redirect URL for the mobile app plus the raw tokens.
|
|
2402
|
+
*
|
|
2403
|
+
* @param code Authorization code from the provider.
|
|
2404
|
+
* @param proxyState The opaque state value returned by `startFlow`.
|
|
2405
|
+
*/
|
|
2406
|
+
handleCallback(code: string, proxyState: string): Promise<{
|
|
2407
|
+
redirectUrl: string;
|
|
2408
|
+
tokens: ProxyTokens;
|
|
2409
|
+
}>;
|
|
2410
|
+
/** Route HTTP requests to the proxy endpoints. Returns null if no match. */
|
|
2411
|
+
handleRequest(request: Request): Promise<Response | null>;
|
|
2412
|
+
}
|
|
2413
|
+
declare function createOAuthProxyModule(config: OAuthProxyConfig, providers: Record<string, OAuthProvider>,
|
|
2414
|
+
/** Base URL of the TheAuth server, e.g. "https://auth.example.com". */
|
|
2415
|
+
baseUrl: string): OAuthProxyModule;
|
|
2416
|
+
declare class OAuthProxyError extends Error {
|
|
2417
|
+
readonly code: string;
|
|
2418
|
+
constructor(code: string, message: string);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
interface OAuthProxyPluginConfig extends OAuthProxyConfig {
|
|
2422
|
+
/**
|
|
2423
|
+
* Provider instances to make available for the proxy.
|
|
2424
|
+
* Keys are the provider IDs used in the `provider` query parameter.
|
|
2425
|
+
*
|
|
2426
|
+
* @example
|
|
2427
|
+
* ```typescript
|
|
2428
|
+
* import { createGoogleProvider } from '@glinr/theauth/auth/oauth/providers/google';
|
|
2429
|
+
*
|
|
2430
|
+
* oauthProxy({
|
|
2431
|
+
* providers: {
|
|
2432
|
+
* google: createGoogleProvider({ clientId: '...', clientSecret: '...' }),
|
|
2433
|
+
* },
|
|
2434
|
+
* allowedRedirectUris: ['com.example.app://callback'],
|
|
2435
|
+
* })
|
|
2436
|
+
* ```
|
|
2437
|
+
*/
|
|
2438
|
+
providers: Record<string, OAuthProvider>;
|
|
2439
|
+
}
|
|
2440
|
+
declare function oauthProxy(config: OAuthProxyPluginConfig): KavachPlugin;
|
|
2441
|
+
|
|
2442
|
+
/**
|
|
2443
|
+
* OIDC Provider module for TheAuth.
|
|
2444
|
+
*
|
|
2445
|
+
* Turns TheAuth into a full OpenID Connect identity provider (IdP).
|
|
2446
|
+
* External applications can register as OIDC clients and authenticate
|
|
2447
|
+
* their users against TheAuth using standard authorization code flow
|
|
2448
|
+
* with PKCE, ID tokens, refresh tokens, discovery, and JWKS.
|
|
2449
|
+
*
|
|
2450
|
+
* @example
|
|
2451
|
+
* ```typescript
|
|
2452
|
+
* import { generateKeyPair } from 'jose';
|
|
2453
|
+
* import { createOidcProviderModule } from '@glinr/theauth/auth';
|
|
2454
|
+
*
|
|
2455
|
+
* const { privateKey } = await generateKeyPair('RS256');
|
|
2456
|
+
* const oidc = createOidcProviderModule(
|
|
2457
|
+
* {
|
|
2458
|
+
* issuer: 'https://auth.example.com',
|
|
2459
|
+
* signingKey: privateKey,
|
|
2460
|
+
* },
|
|
2461
|
+
* db,
|
|
2462
|
+
* getUserClaims,
|
|
2463
|
+
* );
|
|
2464
|
+
*
|
|
2465
|
+
* // Register a client
|
|
2466
|
+
* const client = await oidc.registerClient({
|
|
2467
|
+
* clientName: 'My App',
|
|
2468
|
+
* redirectUris: ['https://app.example.com/callback'],
|
|
2469
|
+
* });
|
|
2470
|
+
*
|
|
2471
|
+
* // Discovery
|
|
2472
|
+
* const doc = oidc.getDiscoveryDocument();
|
|
2473
|
+
* ```
|
|
2474
|
+
*/
|
|
2475
|
+
|
|
2476
|
+
interface OidcProviderConfig {
|
|
2477
|
+
/** Issuer identifier, e.g. "https://auth.example.com". Must be a URL. */
|
|
2478
|
+
issuer: string;
|
|
2479
|
+
/** Private key used to sign ID tokens and access tokens (RSA or EC). */
|
|
2480
|
+
signingKey: CryptoKey | jose.JWK;
|
|
2481
|
+
/** JWT signing algorithm. Default: 'RS256'. */
|
|
2482
|
+
signingAlgorithm?: string;
|
|
2483
|
+
/** Access token lifetime in seconds. Default: 3600 (1 hour). */
|
|
2484
|
+
accessTokenTtl?: number;
|
|
2485
|
+
/** Refresh token lifetime in seconds. Default: 2592000 (30 days). */
|
|
2486
|
+
refreshTokenTtl?: number;
|
|
2487
|
+
/** Authorization code lifetime in seconds. Default: 600 (10 minutes). */
|
|
2488
|
+
authCodeTtl?: number;
|
|
2489
|
+
/** ID token lifetime in seconds. Default: 3600 (1 hour). */
|
|
2490
|
+
idTokenTtl?: number;
|
|
2491
|
+
/** Scopes this provider supports. Default: ['openid', 'profile', 'email']. */
|
|
2492
|
+
supportedScopes?: string[];
|
|
2493
|
+
}
|
|
2494
|
+
interface RegisterClientInput {
|
|
2495
|
+
clientName: string;
|
|
2496
|
+
redirectUris: string[];
|
|
2497
|
+
grantTypes?: string[];
|
|
2498
|
+
responseTypes?: string[];
|
|
2499
|
+
scopes?: string[];
|
|
2500
|
+
tokenEndpointAuthMethod?: string;
|
|
2501
|
+
}
|
|
2502
|
+
interface OidcClient {
|
|
2503
|
+
clientId: string;
|
|
2504
|
+
clientSecret: string | null;
|
|
2505
|
+
clientName: string;
|
|
2506
|
+
redirectUris: string[];
|
|
2507
|
+
grantTypes: string[];
|
|
2508
|
+
responseTypes: string[];
|
|
2509
|
+
scopes: string[];
|
|
2510
|
+
tokenEndpointAuthMethod: string;
|
|
2511
|
+
createdAt: Date;
|
|
2512
|
+
}
|
|
2513
|
+
interface AuthorizeParams {
|
|
2514
|
+
clientId: string;
|
|
2515
|
+
redirectUri: string;
|
|
2516
|
+
responseType: string;
|
|
2517
|
+
scope: string;
|
|
2518
|
+
state?: string;
|
|
2519
|
+
nonce?: string;
|
|
2520
|
+
codeChallenge?: string;
|
|
2521
|
+
codeChallengeMethod?: string;
|
|
2522
|
+
/** The authenticated user's ID. Must be resolved before calling authorize. */
|
|
2523
|
+
userId: string;
|
|
2524
|
+
}
|
|
2525
|
+
interface TokenParams {
|
|
2526
|
+
grantType: string;
|
|
2527
|
+
code?: string;
|
|
2528
|
+
redirectUri?: string;
|
|
2529
|
+
codeVerifier?: string;
|
|
2530
|
+
refreshToken?: string;
|
|
2531
|
+
clientId: string;
|
|
2532
|
+
clientSecret?: string;
|
|
2533
|
+
}
|
|
2534
|
+
interface TokenResponse {
|
|
2535
|
+
accessToken: string;
|
|
2536
|
+
idToken: string;
|
|
2537
|
+
refreshToken: string;
|
|
2538
|
+
tokenType: "Bearer";
|
|
2539
|
+
expiresIn: number;
|
|
2540
|
+
}
|
|
2541
|
+
interface UserInfoClaims {
|
|
2542
|
+
sub: string;
|
|
2543
|
+
email?: string;
|
|
2544
|
+
name?: string;
|
|
2545
|
+
picture?: string;
|
|
2546
|
+
emailVerified?: boolean;
|
|
2547
|
+
}
|
|
2548
|
+
interface AccessTokenClaims {
|
|
2549
|
+
sub: string;
|
|
2550
|
+
iss: string;
|
|
2551
|
+
aud: string;
|
|
2552
|
+
exp: number;
|
|
2553
|
+
iat: number;
|
|
2554
|
+
jti: string;
|
|
2555
|
+
scope: string;
|
|
2556
|
+
clientId: string;
|
|
2557
|
+
}
|
|
2558
|
+
interface OidcDiscoveryDocument {
|
|
2559
|
+
issuer: string;
|
|
2560
|
+
authorization_endpoint: string;
|
|
2561
|
+
token_endpoint: string;
|
|
2562
|
+
userinfo_endpoint: string;
|
|
2563
|
+
jwks_uri: string;
|
|
2564
|
+
registration_endpoint: string;
|
|
2565
|
+
scopes_supported: string[];
|
|
2566
|
+
response_types_supported: string[];
|
|
2567
|
+
grant_types_supported: string[];
|
|
2568
|
+
subject_types_supported: string[];
|
|
2569
|
+
id_token_signing_alg_values_supported: string[];
|
|
2570
|
+
token_endpoint_auth_methods_supported: string[];
|
|
2571
|
+
claims_supported: string[];
|
|
2572
|
+
code_challenge_methods_supported: string[];
|
|
2573
|
+
}
|
|
2574
|
+
interface JsonWebKeySet {
|
|
2575
|
+
keys: jose.JWK[];
|
|
2576
|
+
}
|
|
2577
|
+
/** Callback to resolve user claims for ID tokens and the userinfo endpoint. */
|
|
2578
|
+
type GetUserClaimsFn = (userId: string, scopes: string[]) => Promise<UserInfoClaims>;
|
|
2579
|
+
interface OidcProviderModule {
|
|
2580
|
+
registerClient(input: RegisterClientInput): Promise<Result<OidcClient>>;
|
|
2581
|
+
getClient(clientId: string): Promise<Result<OidcClient>>;
|
|
2582
|
+
deleteClient(clientId: string): Promise<Result<void>>;
|
|
2583
|
+
authorize(params: AuthorizeParams): Promise<Result<{
|
|
2584
|
+
code: string;
|
|
2585
|
+
state?: string;
|
|
2586
|
+
}>>;
|
|
2587
|
+
exchangeToken(params: TokenParams): Promise<Result<TokenResponse>>;
|
|
2588
|
+
getUserInfo(accessToken: string): Promise<Result<UserInfoClaims>>;
|
|
2589
|
+
getDiscoveryDocument(): OidcDiscoveryDocument;
|
|
2590
|
+
getJwks(): Promise<JsonWebKeySet>;
|
|
2591
|
+
validateAccessToken(token: string): Promise<Result<AccessTokenClaims>>;
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Create an OIDC Provider module that turns TheAuth into an identity provider.
|
|
2595
|
+
*
|
|
2596
|
+
* @param config Provider configuration (issuer, signing key, TTLs).
|
|
2597
|
+
* @param db Drizzle database instance.
|
|
2598
|
+
* @param getUserClaims Callback that resolves user claims given a userId and scopes.
|
|
2599
|
+
*/
|
|
2600
|
+
declare function createOidcProviderModule(config: OidcProviderConfig, db: Database, getUserClaims: GetUserClaimsFn): OidcProviderModule;
|
|
2601
|
+
|
|
2602
|
+
/**
|
|
2603
|
+
* Google One Tap authentication for TheAuth.
|
|
2604
|
+
*
|
|
2605
|
+
* Verifies a Google ID token (issued by the Google Identity Services JS
|
|
2606
|
+
* library) server-side via Google's public JWKS endpoint. No Google SDK
|
|
2607
|
+
* required. Uses `jose` for JWT verification — the same library used
|
|
2608
|
+
* elsewhere in TheAuth.
|
|
2609
|
+
*
|
|
2610
|
+
* Flow:
|
|
2611
|
+
* 1. Front-end includes the Google Identity Services script and mounts the
|
|
2612
|
+
* One Tap prompt.
|
|
2613
|
+
* 2. On sign-in the browser POSTs the `credential` (ID token) to
|
|
2614
|
+
* POST /auth/one-tap/callback.
|
|
2615
|
+
* 3. This module verifies the token, finds or creates the user, and returns
|
|
2616
|
+
* a session.
|
|
2617
|
+
*
|
|
2618
|
+
* CSRF: Google's JS library sets a `g_csrf_token` cookie and includes the
|
|
2619
|
+
* same value in the POST body. Both must match.
|
|
2620
|
+
*
|
|
2621
|
+
* @example
|
|
2622
|
+
* ```typescript
|
|
2623
|
+
* const kavach = await createKavach({
|
|
2624
|
+
* database: { provider: 'sqlite', url: 'kavach.db' },
|
|
2625
|
+
* auth: { session: { secret: process.env.SESSION_SECRET } },
|
|
2626
|
+
* });
|
|
2627
|
+
*
|
|
2628
|
+
* // Use via plugin
|
|
2629
|
+
* import { oneTap } from '@glinr/theauth/auth';
|
|
2630
|
+
* // plugins: [oneTap({ clientId: process.env.GOOGLE_CLIENT_ID })]
|
|
2631
|
+
*
|
|
2632
|
+
* // Or use the module directly
|
|
2633
|
+
* const tap = createOneTapModule({ clientId: '...' }, db, sessionManager);
|
|
2634
|
+
* const googleUser = await tap.verify(idToken);
|
|
2635
|
+
* ```
|
|
2636
|
+
*/
|
|
2637
|
+
|
|
2638
|
+
interface OneTapConfig {
|
|
2639
|
+
/** Google OAuth client ID */
|
|
2640
|
+
clientId: string;
|
|
2641
|
+
/** Auto-create user if not found (default: true) */
|
|
2642
|
+
autoCreateUser?: boolean;
|
|
2643
|
+
/** CSRF token cookie name (default: "g_csrf_token") */
|
|
2644
|
+
csrfCookieName?: string;
|
|
2645
|
+
}
|
|
2646
|
+
interface GoogleUser {
|
|
2647
|
+
/** Google user ID (stable, use this as the external ID) */
|
|
2648
|
+
sub: string;
|
|
2649
|
+
email: string;
|
|
2650
|
+
emailVerified: boolean;
|
|
2651
|
+
name: string;
|
|
2652
|
+
givenName?: string;
|
|
2653
|
+
familyName?: string;
|
|
2654
|
+
picture?: string;
|
|
2655
|
+
}
|
|
2656
|
+
interface OneTapModule {
|
|
2657
|
+
/**
|
|
2658
|
+
* Verify a Google ID token and return the decoded user claims.
|
|
2659
|
+
* Throws if the token is invalid, expired, or issued for the wrong audience.
|
|
2660
|
+
*/
|
|
2661
|
+
verify(idToken: string): Promise<GoogleUser>;
|
|
2662
|
+
/**
|
|
2663
|
+
* Handle the POST callback from Google's JS library.
|
|
2664
|
+
*
|
|
2665
|
+
* Expects `application/x-www-form-urlencoded` with `credential` and
|
|
2666
|
+
* `g_csrf_token` fields (plus the matching CSRF cookie). Returns a JSON
|
|
2667
|
+
* response with `{ user, session }` on success or null when the path does
|
|
2668
|
+
* not match (allowing fall-through to other handlers).
|
|
2669
|
+
*/
|
|
2670
|
+
handleRequest(request: Request): Promise<Response | null>;
|
|
2671
|
+
}
|
|
2672
|
+
declare class OneTapVerifyError extends Error {
|
|
2673
|
+
readonly code: string;
|
|
2674
|
+
constructor(message: string, code: string);
|
|
2675
|
+
}
|
|
2676
|
+
declare function createOneTapModule(config: OneTapConfig, db: Database, sessionManager: SessionManager): OneTapModule;
|
|
2677
|
+
|
|
2678
|
+
declare function oneTap(config: OneTapConfig): KavachPlugin;
|
|
2679
|
+
|
|
2680
|
+
/**
|
|
2681
|
+
* OpenAPI 3.1 spec generation plugin for TheAuth.
|
|
2682
|
+
*
|
|
2683
|
+
* Generates a complete OpenAPI document from TheAuth's registered auth
|
|
2684
|
+
* endpoints. Useful for serving at `/api/kavach/openapi.json` or wiring
|
|
2685
|
+
* into Swagger UI / Scalar.
|
|
2686
|
+
*
|
|
2687
|
+
* @example
|
|
2688
|
+
* ```typescript
|
|
2689
|
+
* import { createOpenApiModule } from '@glinr/theauth/auth';
|
|
2690
|
+
*
|
|
2691
|
+
* const openapi = createOpenApiModule();
|
|
2692
|
+
* const spec = openapi.generateSpec({
|
|
2693
|
+
* title: 'My App Auth API',
|
|
2694
|
+
* serverUrl: 'https://api.example.com',
|
|
2695
|
+
* include: ['auth', 'sessions', 'api-keys'],
|
|
2696
|
+
* });
|
|
2697
|
+
*
|
|
2698
|
+
* // In your request handler:
|
|
2699
|
+
* const response = openapi.handleRequest(request);
|
|
2700
|
+
* if (response) return response;
|
|
2701
|
+
* ```
|
|
2702
|
+
*/
|
|
2703
|
+
type EndpointGroup = "agents" | "auth" | "oauth" | "mcp" | "admin" | "organizations" | "sessions" | "api-keys" | "webhooks";
|
|
2704
|
+
interface OpenApiConfig {
|
|
2705
|
+
/** API title shown in spec. Default: "TheAuth API" */
|
|
2706
|
+
title?: string;
|
|
2707
|
+
/** Spec version string. Default: "0.0.1" */
|
|
2708
|
+
version?: string;
|
|
2709
|
+
/** Short description of the API */
|
|
2710
|
+
description?: string;
|
|
2711
|
+
/** Server base URL. Default: "/" */
|
|
2712
|
+
serverUrl?: string;
|
|
2713
|
+
/** Path prefix for all TheAuth endpoints. Default: "/api/kavach" */
|
|
2714
|
+
basePath?: string;
|
|
2715
|
+
/**
|
|
2716
|
+
* Limit which endpoint groups are included in the spec.
|
|
2717
|
+
* When omitted all groups are included.
|
|
2718
|
+
*/
|
|
2719
|
+
include?: EndpointGroup[];
|
|
2720
|
+
}
|
|
2721
|
+
interface OpenApiInfo {
|
|
2722
|
+
title: string;
|
|
2723
|
+
version: string;
|
|
2724
|
+
description?: string;
|
|
2725
|
+
}
|
|
2726
|
+
interface OpenApiServer {
|
|
2727
|
+
url: string;
|
|
2728
|
+
}
|
|
2729
|
+
interface OpenApiSchema {
|
|
2730
|
+
type?: string;
|
|
2731
|
+
properties?: Record<string, OpenApiSchema>;
|
|
2732
|
+
required?: string[];
|
|
2733
|
+
items?: OpenApiSchema;
|
|
2734
|
+
description?: string;
|
|
2735
|
+
example?: unknown;
|
|
2736
|
+
enum?: unknown[];
|
|
2737
|
+
format?: string;
|
|
2738
|
+
nullable?: boolean;
|
|
2739
|
+
additionalProperties?: boolean | OpenApiSchema;
|
|
2740
|
+
oneOf?: OpenApiSchema[];
|
|
2741
|
+
}
|
|
2742
|
+
interface OpenApiMediaType {
|
|
2743
|
+
schema: OpenApiSchema;
|
|
2744
|
+
}
|
|
2745
|
+
interface OpenApiRequestBody {
|
|
2746
|
+
required?: boolean;
|
|
2747
|
+
content: Record<string, OpenApiMediaType>;
|
|
2748
|
+
}
|
|
2749
|
+
interface OpenApiResponse {
|
|
2750
|
+
description: string;
|
|
2751
|
+
content?: Record<string, OpenApiMediaType>;
|
|
2752
|
+
}
|
|
2753
|
+
interface OpenApiSecurityRequirement {
|
|
2754
|
+
[schemeName: string]: string[];
|
|
2755
|
+
}
|
|
2756
|
+
interface OpenApiOperation {
|
|
2757
|
+
operationId: string;
|
|
2758
|
+
summary: string;
|
|
2759
|
+
tags: string[];
|
|
2760
|
+
security?: OpenApiSecurityRequirement[];
|
|
2761
|
+
requestBody?: OpenApiRequestBody;
|
|
2762
|
+
responses: Record<string, OpenApiResponse>;
|
|
2763
|
+
parameters?: OpenApiParameter[];
|
|
2764
|
+
}
|
|
2765
|
+
interface OpenApiParameter {
|
|
2766
|
+
name: string;
|
|
2767
|
+
in: "path" | "query" | "header" | "cookie";
|
|
2768
|
+
required?: boolean;
|
|
2769
|
+
schema: OpenApiSchema;
|
|
2770
|
+
description?: string;
|
|
2771
|
+
}
|
|
2772
|
+
interface OpenApiPathItem {
|
|
2773
|
+
get?: OpenApiOperation;
|
|
2774
|
+
post?: OpenApiOperation;
|
|
2775
|
+
put?: OpenApiOperation;
|
|
2776
|
+
patch?: OpenApiOperation;
|
|
2777
|
+
delete?: OpenApiOperation;
|
|
2778
|
+
}
|
|
2779
|
+
interface OpenApiSecurityScheme {
|
|
2780
|
+
type: string;
|
|
2781
|
+
scheme?: string;
|
|
2782
|
+
bearerFormat?: string;
|
|
2783
|
+
description?: string;
|
|
2784
|
+
in?: string;
|
|
2785
|
+
name?: string;
|
|
2786
|
+
flows?: Record<string, unknown>;
|
|
2787
|
+
}
|
|
2788
|
+
interface OpenApiComponents {
|
|
2789
|
+
securitySchemes: Record<string, OpenApiSecurityScheme>;
|
|
2790
|
+
schemas: Record<string, OpenApiSchema>;
|
|
2791
|
+
}
|
|
2792
|
+
interface OpenApiDocument {
|
|
2793
|
+
openapi: "3.1.0";
|
|
2794
|
+
info: OpenApiInfo;
|
|
2795
|
+
servers: OpenApiServer[];
|
|
2796
|
+
paths: Record<string, OpenApiPathItem>;
|
|
2797
|
+
components: OpenApiComponents;
|
|
2798
|
+
tags: Array<{
|
|
2799
|
+
name: string;
|
|
2800
|
+
description?: string;
|
|
2801
|
+
}>;
|
|
2802
|
+
}
|
|
2803
|
+
interface OpenApiModule {
|
|
2804
|
+
/** Generate a complete OpenAPI 3.1.0 document */
|
|
2805
|
+
generateSpec(config?: OpenApiConfig): OpenApiDocument;
|
|
2806
|
+
/**
|
|
2807
|
+
* Handle an HTTP request for the spec JSON.
|
|
2808
|
+
*
|
|
2809
|
+
* Returns a `Response` with the spec when the request path ends with
|
|
2810
|
+
* `/openapi.json`. Returns `null` for any other path so the caller's
|
|
2811
|
+
* routing continues normally.
|
|
2812
|
+
*/
|
|
2813
|
+
handleRequest(request: Request, config?: OpenApiConfig): Response | null;
|
|
2814
|
+
}
|
|
2815
|
+
/**
|
|
2816
|
+
* Create an OpenAPI module that generates TheAuth API specifications.
|
|
2817
|
+
*
|
|
2818
|
+
* The returned module is stateless and safe to share across requests.
|
|
2819
|
+
*/
|
|
2820
|
+
declare function createOpenApiModule(): OpenApiModule;
|
|
2821
|
+
|
|
2822
|
+
declare function organization(config?: OrgConfig): KavachPlugin;
|
|
2823
|
+
|
|
2824
|
+
declare function passkey(config: PasskeyConfig): KavachPlugin;
|
|
2825
|
+
|
|
2826
|
+
/**
|
|
2827
|
+
* Polar payment integration for TheAuth.
|
|
2828
|
+
*
|
|
2829
|
+
* Links Polar customers to TheAuth users, handles subscription lifecycle
|
|
2830
|
+
* webhooks, and stores subscription status. Uses Polar's REST API directly
|
|
2831
|
+
* via fetch — no Polar SDK dependency.
|
|
2832
|
+
*
|
|
2833
|
+
* @example
|
|
2834
|
+
* ```typescript
|
|
2835
|
+
* import { createPolarModule } from '@glinr/theauth/auth';
|
|
2836
|
+
*
|
|
2837
|
+
* const polar = createPolarModule({
|
|
2838
|
+
* accessToken: process.env.POLAR_ACCESS_TOKEN!,
|
|
2839
|
+
* webhookSecret: process.env.POLAR_WEBHOOK_SECRET!,
|
|
2840
|
+
* sandbox: process.env.NODE_ENV !== 'production',
|
|
2841
|
+
* onSubscriptionChange: async (userId, sub) => {
|
|
2842
|
+
* console.log(`User ${userId} subscription: ${sub.status}`);
|
|
2843
|
+
* },
|
|
2844
|
+
* }, db);
|
|
2845
|
+
*
|
|
2846
|
+
* const { url } = await polar.createCheckout(userId, 'product_xxx', {
|
|
2847
|
+
* successUrl: 'https://example.com/success',
|
|
2848
|
+
* });
|
|
2849
|
+
* ```
|
|
2850
|
+
*/
|
|
2851
|
+
|
|
2852
|
+
interface PolarConfig {
|
|
2853
|
+
/** Polar access token */
|
|
2854
|
+
accessToken: string;
|
|
2855
|
+
/** Polar webhook secret for HMAC-SHA256 signature verification */
|
|
2856
|
+
webhookSecret: string;
|
|
2857
|
+
/** Optional organization ID to scope requests */
|
|
2858
|
+
organizationId?: string;
|
|
2859
|
+
/** Use sandbox.polar.sh instead of polar.sh (default: false) */
|
|
2860
|
+
sandbox?: boolean;
|
|
2861
|
+
/** Callback fired whenever subscription data changes for a user */
|
|
2862
|
+
onSubscriptionChange?: (userId: string, subscription: PolarSubscription) => Promise<void>;
|
|
2863
|
+
}
|
|
2864
|
+
interface PolarSubscription {
|
|
2865
|
+
id: string;
|
|
2866
|
+
status: "active" | "canceled" | "incomplete" | "past_due" | "trialing" | "unpaid";
|
|
2867
|
+
productId: string;
|
|
2868
|
+
currentPeriodEnd: Date;
|
|
2869
|
+
cancelAtPeriodEnd: boolean;
|
|
2870
|
+
}
|
|
2871
|
+
interface PolarModule {
|
|
2872
|
+
/** Create a Polar checkout session and return its URL + ID */
|
|
2873
|
+
createCheckout(userId: string, productId: string, options?: {
|
|
2874
|
+
successUrl?: string;
|
|
2875
|
+
customerEmail?: string;
|
|
2876
|
+
}): Promise<{
|
|
2877
|
+
url: string;
|
|
2878
|
+
id: string;
|
|
2879
|
+
}>;
|
|
2880
|
+
/** Return current subscription info for a user from the database */
|
|
2881
|
+
getSubscription(userId: string): Promise<PolarSubscription | null>;
|
|
2882
|
+
/** Verify Polar webhook signature and dispatch to internal handlers */
|
|
2883
|
+
handleWebhook(request: Request): Promise<Response>;
|
|
2884
|
+
/** Route an incoming HTTP request to the appropriate handler (returns null if path unmatched) */
|
|
2885
|
+
handleRequest(request: Request): Promise<Response | null>;
|
|
2886
|
+
}
|
|
2887
|
+
declare function createPolarModule(config: PolarConfig, db: Database): PolarModule;
|
|
2888
|
+
|
|
2889
|
+
declare function polar(config: PolarConfig): KavachPlugin;
|
|
2890
|
+
|
|
2891
|
+
/**
|
|
2892
|
+
* Shared store interface for the rateLimit() plugin.
|
|
2893
|
+
* Kept in a separate file to avoid circular imports between
|
|
2894
|
+
* rate-limit.ts and the individual store implementations.
|
|
2895
|
+
*/
|
|
2896
|
+
/** Store backend interface for the rateLimit() plugin. */
|
|
2897
|
+
interface RateLimitStore {
|
|
2898
|
+
/**
|
|
2899
|
+
* Increment the hit count for `key` within a `windowMs`-millisecond window.
|
|
2900
|
+
* Returns the updated count and the unix timestamp (ms) when the window resets.
|
|
2901
|
+
*/
|
|
2902
|
+
increment(key: string, windowMs: number): Promise<{
|
|
2903
|
+
count: number;
|
|
2904
|
+
resetAt: number;
|
|
2905
|
+
}>;
|
|
2906
|
+
/** Clear all recorded hits for a key. */
|
|
2907
|
+
reset(key: string): Promise<void>;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
/**
|
|
2911
|
+
* rateLimit() plugin for TheAuth.
|
|
2912
|
+
*
|
|
2913
|
+
* Wraps auth endpoints with configurable per-IP (and per-agent) throttling.
|
|
2914
|
+
* Intercepts requests via the onRequest lifecycle hook before any handler
|
|
2915
|
+
* runs, keeping the limiting logic decoupled from individual endpoints.
|
|
2916
|
+
*
|
|
2917
|
+
* @example
|
|
2918
|
+
* ```typescript
|
|
2919
|
+
* import { createKavach } from '@glinr/theauth';
|
|
2920
|
+
* import { rateLimit } from '@glinr/theauth/auth';
|
|
2921
|
+
* import { kvStore } from '@glinr/theauth/auth/stores/kv';
|
|
2922
|
+
*
|
|
2923
|
+
* const kavach = createKavach({
|
|
2924
|
+
* plugins: [
|
|
2925
|
+
* rateLimit({
|
|
2926
|
+
* signIn: { window: '15m', max: 10 },
|
|
2927
|
+
* signUp: { window: '1h', max: 5 },
|
|
2928
|
+
* passwordReset: { window: '1h', max: 3 },
|
|
2929
|
+
* agentAuthorize:{ window: '1m', max: 100 },
|
|
2930
|
+
* default: { window: '1m', max: 60 },
|
|
2931
|
+
* store: kvStore(env.CACHE_KV),
|
|
2932
|
+
* }),
|
|
2933
|
+
* ],
|
|
2934
|
+
* });
|
|
2935
|
+
* ```
|
|
2936
|
+
*/
|
|
2937
|
+
|
|
2938
|
+
/** Per-endpoint rate limit configuration */
|
|
2939
|
+
interface EndpointLimit {
|
|
2940
|
+
/** Duration string: "15m", "1h", "30s", "1d" */
|
|
2941
|
+
window: string;
|
|
2942
|
+
/** Maximum number of requests allowed within the window */
|
|
2943
|
+
max: number;
|
|
2944
|
+
}
|
|
2945
|
+
interface RateLimitConfig$1 {
|
|
2946
|
+
/** Limit for POST /auth/sign-in */
|
|
2947
|
+
signIn?: EndpointLimit;
|
|
2948
|
+
/** Limit for POST /auth/sign-up */
|
|
2949
|
+
signUp?: EndpointLimit;
|
|
2950
|
+
/** Limit for POST /auth/password-reset */
|
|
2951
|
+
passwordReset?: EndpointLimit;
|
|
2952
|
+
/** Limit for POST /auth/agent/authorize */
|
|
2953
|
+
agentAuthorize?: EndpointLimit;
|
|
2954
|
+
/** Fallback limit applied to all other /auth/* paths */
|
|
2955
|
+
default?: EndpointLimit;
|
|
2956
|
+
/**
|
|
2957
|
+
* Storage backend.
|
|
2958
|
+
* Pass "memory" or omit to use the built-in in-memory store.
|
|
2959
|
+
* Pass a KVStore (or any RateLimitStore) for edge deployments.
|
|
2960
|
+
*/
|
|
2961
|
+
store?: "memory" | RateLimitStore;
|
|
2962
|
+
/**
|
|
2963
|
+
* Extract a rate-limit key from the request.
|
|
2964
|
+
* Defaults to reading x-forwarded-for → x-real-ip → "unknown".
|
|
2965
|
+
*/
|
|
2966
|
+
keyExtractor?: (request: Request) => string;
|
|
2967
|
+
/**
|
|
2968
|
+
* Custom response factory called when a limit is exceeded.
|
|
2969
|
+
* Defaults to a JSON 429 response with Retry-After header.
|
|
2970
|
+
*/
|
|
2971
|
+
onLimit?: (request: Request, retryAfter: number) => Response;
|
|
2972
|
+
}
|
|
2973
|
+
declare function rateLimit(config?: RateLimitConfig$1): KavachPlugin;
|
|
2974
|
+
|
|
2975
|
+
/**
|
|
2976
|
+
* In-memory sliding window rate limiter for auth endpoints.
|
|
2977
|
+
*
|
|
2978
|
+
* Uses a Map keyed by the caller-supplied string (typically an IP address).
|
|
2979
|
+
* Each entry stores a list of request timestamps within the current window.
|
|
2980
|
+
* Expired timestamps are pruned on every check so memory stays bounded.
|
|
2981
|
+
*/
|
|
2982
|
+
interface RateLimitConfig {
|
|
2983
|
+
/** Max requests allowed within the window */
|
|
2984
|
+
max: number;
|
|
2985
|
+
/** Window duration in seconds */
|
|
2986
|
+
window: number;
|
|
2987
|
+
}
|
|
2988
|
+
interface RateLimitResult {
|
|
2989
|
+
allowed: boolean;
|
|
2990
|
+
/** Requests remaining in the current window */
|
|
2991
|
+
remaining: number;
|
|
2992
|
+
/** When the oldest in-window request expires and a slot re-opens */
|
|
2993
|
+
resetAt: Date;
|
|
2994
|
+
}
|
|
2995
|
+
interface RateLimiter {
|
|
2996
|
+
/** Check whether the key is within its limit. Consumes one slot when allowed. */
|
|
2997
|
+
check(key: string): RateLimitResult;
|
|
2998
|
+
/** Clear all recorded hits for a key (useful in tests or on successful auth). */
|
|
2999
|
+
reset(key: string): void;
|
|
3000
|
+
}
|
|
3001
|
+
declare function createRateLimiter(config: RateLimitConfig): RateLimiter;
|
|
3002
|
+
|
|
3003
|
+
/**
|
|
3004
|
+
* Higher-order function that wraps a plugin endpoint handler with IP-based
|
|
3005
|
+
* rate limiting. When the limit is exceeded it responds with 429 and a
|
|
3006
|
+
* Retry-After header before the wrapped handler is ever called.
|
|
3007
|
+
*/
|
|
3008
|
+
|
|
3009
|
+
interface RateLimitMiddlewareOptions {
|
|
3010
|
+
/**
|
|
3011
|
+
* Derive the rate-limit key from the incoming request.
|
|
3012
|
+
*
|
|
3013
|
+
* Defaults to the first non-empty value of:
|
|
3014
|
+
* x-forwarded-for → first IP in the comma-separated list
|
|
3015
|
+
* x-real-ip
|
|
3016
|
+
* "unknown"
|
|
3017
|
+
*/
|
|
3018
|
+
keyExtractor?: (request: Request) => string;
|
|
3019
|
+
}
|
|
3020
|
+
declare function withRateLimit(handler: PluginEndpoint["handler"], limiter: RateLimiter, options?: RateLimitMiddlewareOptions): PluginEndpoint["handler"];
|
|
3021
|
+
|
|
3022
|
+
/**
|
|
3023
|
+
* Relationship-Based Access Control (ReBAC) engine for TheAuth.
|
|
3024
|
+
*
|
|
3025
|
+
* Inspired by Google Zanzibar. Models authorization as a graph of typed
|
|
3026
|
+
* relationships between subjects (users, agents, teams) and objects
|
|
3027
|
+
* (orgs, workspaces, projects, documents). Permission checks traverse the
|
|
3028
|
+
* graph, following both direct relationships and parent-child inheritance.
|
|
3029
|
+
*
|
|
3030
|
+
* Key ideas:
|
|
3031
|
+
* - Resources live in a hierarchy (org > workspace > project > document).
|
|
3032
|
+
* - Relationships connect subjects to objects with a named relation.
|
|
3033
|
+
* - Permission rules define how relations compose. An "editor" implicitly
|
|
3034
|
+
* has "viewer" access; a "viewer" on a workspace inherits "viewer" on
|
|
3035
|
+
* child projects.
|
|
3036
|
+
* - Graph traversal is depth-limited to prevent runaway queries.
|
|
3037
|
+
*/
|
|
3038
|
+
|
|
3039
|
+
interface ReBACConfig {
|
|
3040
|
+
/** Maximum graph traversal depth for permission checks (default: 10). */
|
|
3041
|
+
maxDepth?: number;
|
|
3042
|
+
/** Permission rules per resource type. Key is the resource type. */
|
|
3043
|
+
permissionRules?: Record<string, PermissionRuleSet>;
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Defines how relations map to permissions for a given resource type.
|
|
3047
|
+
*
|
|
3048
|
+
* `implies` — relation X implies relation Y (e.g. editor implies viewer).
|
|
3049
|
+
* `inherits` — permission P on this resource's parent also grants P here.
|
|
3050
|
+
*/
|
|
3051
|
+
interface PermissionRuleSet {
|
|
3052
|
+
/** Which relations imply which other relations on the same object. */
|
|
3053
|
+
implies?: Record<string, string[]>;
|
|
3054
|
+
/** Permissions inherited from the parent resource. `true` = all, or list. */
|
|
3055
|
+
inheritFromParent?: boolean | string[];
|
|
3056
|
+
}
|
|
3057
|
+
interface ResourceNode {
|
|
3058
|
+
id: string;
|
|
3059
|
+
type: string;
|
|
3060
|
+
parentId?: string;
|
|
3061
|
+
parentType?: string;
|
|
3062
|
+
}
|
|
3063
|
+
interface Relationship {
|
|
3064
|
+
id: string;
|
|
3065
|
+
subjectType: string;
|
|
3066
|
+
subjectId: string;
|
|
3067
|
+
relation: string;
|
|
3068
|
+
objectType: string;
|
|
3069
|
+
objectId: string;
|
|
3070
|
+
createdAt: Date;
|
|
3071
|
+
}
|
|
3072
|
+
interface CheckParams {
|
|
3073
|
+
subjectType: string;
|
|
3074
|
+
subjectId: string;
|
|
3075
|
+
permission: string;
|
|
3076
|
+
objectType: string;
|
|
3077
|
+
objectId: string;
|
|
3078
|
+
}
|
|
3079
|
+
interface CheckResult {
|
|
3080
|
+
allowed: boolean;
|
|
3081
|
+
path?: string[];
|
|
3082
|
+
}
|
|
3083
|
+
interface ListObjectsParams {
|
|
3084
|
+
subjectType: string;
|
|
3085
|
+
subjectId: string;
|
|
3086
|
+
permission: string;
|
|
3087
|
+
objectType: string;
|
|
3088
|
+
}
|
|
3089
|
+
interface ListSubjectsParams {
|
|
3090
|
+
objectType: string;
|
|
3091
|
+
objectId: string;
|
|
3092
|
+
permission: string;
|
|
3093
|
+
subjectType: string;
|
|
3094
|
+
}
|
|
3095
|
+
interface ExpandParams {
|
|
3096
|
+
type: string;
|
|
3097
|
+
id: string;
|
|
3098
|
+
}
|
|
3099
|
+
interface ReBACModule {
|
|
3100
|
+
/** Register a resource in the hierarchy. */
|
|
3101
|
+
createResource(node: ResourceNode): Promise<Result<ResourceNode>>;
|
|
3102
|
+
/** Remove a resource and all its relationships. */
|
|
3103
|
+
deleteResource(type: string, id: string): Promise<Result<void>>;
|
|
3104
|
+
/** Get a resource by type and id. */
|
|
3105
|
+
getResource(type: string, id: string): Promise<Result<ResourceNode | null>>;
|
|
3106
|
+
/** Create a relationship between a subject and an object. */
|
|
3107
|
+
addRelationship(rel: Omit<Relationship, "id" | "createdAt">): Promise<Result<Relationship>>;
|
|
3108
|
+
/** Remove a specific relationship. */
|
|
3109
|
+
removeRelationship(subjectType: string, subjectId: string, relation: string, objectType: string, objectId: string): Promise<Result<void>>;
|
|
3110
|
+
/**
|
|
3111
|
+
* Check whether a subject has a permission on an object.
|
|
3112
|
+
* Returns the relationship path when access is granted.
|
|
3113
|
+
*/
|
|
3114
|
+
check(params: CheckParams): Promise<Result<CheckResult>>;
|
|
3115
|
+
/** List all object IDs of a given type that a subject can access with a permission. */
|
|
3116
|
+
listObjects(params: ListObjectsParams): Promise<Result<string[]>>;
|
|
3117
|
+
/** List all subject IDs of a given type that hold a permission on an object. */
|
|
3118
|
+
listSubjects(params: ListSubjectsParams): Promise<Result<string[]>>;
|
|
3119
|
+
/** Return all relationships where the given entity is subject or object. */
|
|
3120
|
+
expand(params: ExpandParams): Promise<Result<Relationship[]>>;
|
|
3121
|
+
}
|
|
3122
|
+
declare function createReBACModule(config: ReBACConfig, db: Database): ReBACModule;
|
|
3123
|
+
|
|
3124
|
+
/**
|
|
3125
|
+
* SCIM 2.0 directory sync for TheAuth.
|
|
3126
|
+
*
|
|
3127
|
+
* Implements RFC 7644 (SCIM 2.0 protocol) to allow enterprise identity
|
|
3128
|
+
* providers (Okta, Azure AD, Google Workspace) to provision and deprovision
|
|
3129
|
+
* users and groups automatically.
|
|
3130
|
+
*
|
|
3131
|
+
* Users map to kavach_users. Groups map to kavach_organizations.
|
|
3132
|
+
*
|
|
3133
|
+
* @example
|
|
3134
|
+
* ```typescript
|
|
3135
|
+
* import { createScimModule } from '@glinr/theauth/auth';
|
|
3136
|
+
*
|
|
3137
|
+
* const scim = createScimModule({
|
|
3138
|
+
* bearerToken: process.env.SCIM_TOKEN,
|
|
3139
|
+
* onProvision: async (user) => {
|
|
3140
|
+
* console.log('Provisioned:', user.userName);
|
|
3141
|
+
* },
|
|
3142
|
+
* }, db);
|
|
3143
|
+
*
|
|
3144
|
+
* // In your request handler:
|
|
3145
|
+
* const response = await scim.handleRequest(request);
|
|
3146
|
+
* ```
|
|
3147
|
+
*/
|
|
3148
|
+
|
|
3149
|
+
interface ScimConfig {
|
|
3150
|
+
/** Bearer token for SCIM API authentication */
|
|
3151
|
+
bearerToken: string;
|
|
3152
|
+
/** Whether to auto-create users on provision (default: true) */
|
|
3153
|
+
autoCreateUsers?: boolean;
|
|
3154
|
+
/** Whether to auto-deactivate users on deprovision (default: true) */
|
|
3155
|
+
autoDeactivateUsers?: boolean;
|
|
3156
|
+
/** Callback when user is provisioned */
|
|
3157
|
+
onProvision?: (user: ScimUser) => Promise<void>;
|
|
3158
|
+
/** Callback when user is deprovisioned */
|
|
3159
|
+
onDeprovision?: (userId: string) => Promise<void>;
|
|
3160
|
+
}
|
|
3161
|
+
interface ScimUser {
|
|
3162
|
+
id: string;
|
|
3163
|
+
userName: string;
|
|
3164
|
+
name?: {
|
|
3165
|
+
givenName?: string;
|
|
3166
|
+
familyName?: string;
|
|
3167
|
+
};
|
|
3168
|
+
emails?: Array<{
|
|
3169
|
+
value: string;
|
|
3170
|
+
primary?: boolean;
|
|
3171
|
+
}>;
|
|
3172
|
+
active?: boolean;
|
|
3173
|
+
externalId?: string;
|
|
3174
|
+
}
|
|
3175
|
+
interface ScimGroup {
|
|
3176
|
+
id: string;
|
|
3177
|
+
displayName: string;
|
|
3178
|
+
externalId?: string;
|
|
3179
|
+
members?: Array<{
|
|
3180
|
+
value: string;
|
|
3181
|
+
display?: string;
|
|
3182
|
+
}>;
|
|
3183
|
+
}
|
|
3184
|
+
interface ScimModule {
|
|
3185
|
+
handleRequest(request: Request): Promise<Response | null>;
|
|
3186
|
+
}
|
|
3187
|
+
declare function createScimModule(config: ScimConfig, db: Database): ScimModule;
|
|
3188
|
+
|
|
3189
|
+
/**
|
|
3190
|
+
* SCIM 2.0 directory sync plugin for TheAuth.
|
|
3191
|
+
*
|
|
3192
|
+
* Mounts SCIM endpoints under `/scim/v2/` so enterprise IdPs (Okta, Azure AD,
|
|
3193
|
+
* Google Workspace) can provision and deprovision users and groups.
|
|
3194
|
+
*
|
|
3195
|
+
* All endpoints require a static Bearer token supplied via `config.bearerToken`.
|
|
3196
|
+
*
|
|
3197
|
+
* @example
|
|
3198
|
+
* ```typescript
|
|
3199
|
+
* import { createKavach } from '@glinr/theauth';
|
|
3200
|
+
* import { scim } from '@glinr/theauth/auth';
|
|
3201
|
+
*
|
|
3202
|
+
* const kavach = await createKavach({
|
|
3203
|
+
* database: { provider: 'sqlite', url: 'kavach.db' },
|
|
3204
|
+
* plugins: [
|
|
3205
|
+
* scim({
|
|
3206
|
+
* bearerToken: process.env.SCIM_TOKEN,
|
|
3207
|
+
* onProvision: async (user) => {
|
|
3208
|
+
* await sendWelcomeEmail(user.emails?.[0]?.value);
|
|
3209
|
+
* },
|
|
3210
|
+
* }),
|
|
3211
|
+
* ],
|
|
3212
|
+
* });
|
|
3213
|
+
* ```
|
|
3214
|
+
*/
|
|
3215
|
+
declare function scim(config: ScimConfig): KavachPlugin;
|
|
3216
|
+
|
|
3217
|
+
/**
|
|
3218
|
+
* Sign In With Ethereum (SIWE) for TheAuth.
|
|
3219
|
+
*
|
|
3220
|
+
* Authenticates users by verifying an Ethereum wallet signature per EIP-4361.
|
|
3221
|
+
* Full secp256k1 recovery requires ethers or viem as peer deps — this module
|
|
3222
|
+
* validates message format and nonce integrity. Add a `verify` override via
|
|
3223
|
+
* `verifySignature` option when you want cryptographic proof.
|
|
3224
|
+
*
|
|
3225
|
+
* @example
|
|
3226
|
+
* ```typescript
|
|
3227
|
+
* const siwe = createSiweModule({
|
|
3228
|
+
* domain: 'example.com',
|
|
3229
|
+
* uri: 'https://example.com',
|
|
3230
|
+
* statement: 'Sign in to Example App',
|
|
3231
|
+
* });
|
|
3232
|
+
*
|
|
3233
|
+
* // 1. Frontend requests a nonce
|
|
3234
|
+
* const nonce = await siwe.generateNonce();
|
|
3235
|
+
*
|
|
3236
|
+
* // 2. Frontend builds the message and asks wallet to sign it
|
|
3237
|
+
* const message = siwe.buildMessage('0xAbc...', nonce, 1);
|
|
3238
|
+
*
|
|
3239
|
+
* // 3. Frontend submits message + signature
|
|
3240
|
+
* const { address, chainId } = await siwe.verify(message, signature);
|
|
3241
|
+
* ```
|
|
3242
|
+
*/
|
|
3243
|
+
interface SiweConfig {
|
|
3244
|
+
/** Your app's domain (e.g., "example.com") */
|
|
3245
|
+
domain: string;
|
|
3246
|
+
/** URI (e.g., "https://example.com") */
|
|
3247
|
+
uri: string;
|
|
3248
|
+
/** Statement shown in wallet (optional) */
|
|
3249
|
+
statement?: string;
|
|
3250
|
+
/** Nonce TTL in seconds (default: 300) */
|
|
3251
|
+
nonceTtlSeconds?: number;
|
|
3252
|
+
/**
|
|
3253
|
+
* Optional signature verifier. Called with the EIP-4361 message and
|
|
3254
|
+
* hex signature. Should return the recovered Ethereum address.
|
|
3255
|
+
* When omitted, the module trusts the address from the message body
|
|
3256
|
+
* (suitable for development; add viem/ethers recovery in production).
|
|
3257
|
+
*/
|
|
3258
|
+
verifySignature?: (message: string, signature: string) => Promise<string>;
|
|
3259
|
+
}
|
|
3260
|
+
interface SiweModule {
|
|
3261
|
+
/** Generate a nonce for the SIWE message */
|
|
3262
|
+
generateNonce(): Promise<string>;
|
|
3263
|
+
/** Build the EIP-4361 message for the wallet to sign */
|
|
3264
|
+
buildMessage(address: string, nonce: string, chainId?: number): string;
|
|
3265
|
+
/** Verify the signed message and return the Ethereum address */
|
|
3266
|
+
verify(message: string, signature: string): Promise<{
|
|
3267
|
+
address: string;
|
|
3268
|
+
chainId: number;
|
|
3269
|
+
}>;
|
|
3270
|
+
/** Handle full SIWE auth flow via HTTP */
|
|
3271
|
+
handleRequest(request: Request): Promise<Response | null>;
|
|
3272
|
+
}
|
|
3273
|
+
interface SiweVerifyResult {
|
|
3274
|
+
address: string;
|
|
3275
|
+
chainId: number;
|
|
3276
|
+
}
|
|
3277
|
+
declare function createSiweModule(config: SiweConfig): SiweModule;
|
|
3278
|
+
|
|
3279
|
+
declare function siwe(config: SiweConfig): KavachPlugin;
|
|
3280
|
+
|
|
3281
|
+
/**
|
|
3282
|
+
* Cloudflare KV rate limit store.
|
|
3283
|
+
*
|
|
3284
|
+
* Each key stores a JSON object { count, resetAt } with a TTL so Cloudflare
|
|
3285
|
+
* automatically evicts expired windows without any extra cleanup logic.
|
|
3286
|
+
*
|
|
3287
|
+
* The KV namespace is passed in at construction time so this module remains
|
|
3288
|
+
* free of any Cloudflare-specific imports (it only uses the KV interface that
|
|
3289
|
+
* Workers expose at runtime).
|
|
3290
|
+
*/
|
|
3291
|
+
|
|
3292
|
+
/** Minimal KV namespace interface — compatible with Cloudflare Workers KVNamespace */
|
|
3293
|
+
interface KVNamespace {
|
|
3294
|
+
get(key: string): Promise<string | null>;
|
|
3295
|
+
put(key: string, value: string, options?: {
|
|
3296
|
+
expirationTtl?: number;
|
|
3297
|
+
}): Promise<void>;
|
|
3298
|
+
delete(key: string): Promise<void>;
|
|
3299
|
+
}
|
|
3300
|
+
declare class KVStore implements RateLimitStore {
|
|
3301
|
+
private readonly kv;
|
|
3302
|
+
constructor(kv: KVNamespace);
|
|
3303
|
+
increment(key: string, windowMs: number): Promise<{
|
|
3304
|
+
count: number;
|
|
3305
|
+
resetAt: number;
|
|
3306
|
+
}>;
|
|
3307
|
+
reset(key: string): Promise<void>;
|
|
3308
|
+
}
|
|
3309
|
+
/** Factory function to create a KVStore from a KV namespace binding */
|
|
3310
|
+
declare function kvStore(kv: KVNamespace): KVStore;
|
|
3311
|
+
|
|
3312
|
+
/**
|
|
3313
|
+
* In-memory rate limit store.
|
|
3314
|
+
*
|
|
3315
|
+
* Uses a Map keyed by a caller-supplied string. Each entry tracks the current
|
|
3316
|
+
* hit count and the unix timestamp (ms) at which the window resets. Expired
|
|
3317
|
+
* windows are reset on the next increment so memory stays bounded without
|
|
3318
|
+
* needing a background sweep.
|
|
3319
|
+
*/
|
|
3320
|
+
|
|
3321
|
+
declare class MemoryStore implements RateLimitStore {
|
|
3322
|
+
private readonly entries;
|
|
3323
|
+
increment(key: string, windowMs: number): Promise<{
|
|
3324
|
+
count: number;
|
|
3325
|
+
resetAt: number;
|
|
3326
|
+
}>;
|
|
3327
|
+
reset(key: string): Promise<void>;
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
/**
|
|
3331
|
+
* Stripe payment integration for TheAuth.
|
|
3332
|
+
*
|
|
3333
|
+
* Links Stripe customers to TheAuth users, handles subscription lifecycle
|
|
3334
|
+
* webhooks, and stores subscription status. Uses Stripe's REST API directly
|
|
3335
|
+
* via fetch — no Stripe SDK dependency.
|
|
3336
|
+
*
|
|
3337
|
+
* @example
|
|
3338
|
+
* ```typescript
|
|
3339
|
+
* import { createStripeModule } from '@glinr/theauth/auth';
|
|
3340
|
+
*
|
|
3341
|
+
* const stripe = createStripeModule({
|
|
3342
|
+
* secretKey: process.env.STRIPE_SECRET_KEY!,
|
|
3343
|
+
* webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
3344
|
+
* autoCreateCustomer: true,
|
|
3345
|
+
* }, db);
|
|
3346
|
+
*
|
|
3347
|
+
* const customerId = await stripe.createCustomer(userId, email, name);
|
|
3348
|
+
* const { url } = await stripe.createCheckoutSession(userId, priceId, {
|
|
3349
|
+
* successUrl: 'https://example.com/success',
|
|
3350
|
+
* cancelUrl: 'https://example.com/cancel',
|
|
3351
|
+
* });
|
|
3352
|
+
* ```
|
|
3353
|
+
*/
|
|
3354
|
+
|
|
3355
|
+
interface StripeConfig {
|
|
3356
|
+
/** Stripe secret key (sk_live_... or sk_test_...) */
|
|
3357
|
+
secretKey: string;
|
|
3358
|
+
/** Stripe webhook signing secret (whsec_...) */
|
|
3359
|
+
webhookSecret: string;
|
|
3360
|
+
/** Auto-create a Stripe customer when the user record is referenced (default: true) */
|
|
3361
|
+
autoCreateCustomer?: boolean;
|
|
3362
|
+
/** Stripe API version (default: "2024-12-18.acacia") */
|
|
3363
|
+
apiVersion?: string;
|
|
3364
|
+
/** Callback fired whenever subscription data changes for a user */
|
|
3365
|
+
onSubscriptionChange?: (userId: string, subscription: SubscriptionInfo) => Promise<void>;
|
|
3366
|
+
}
|
|
3367
|
+
interface SubscriptionInfo {
|
|
3368
|
+
id: string;
|
|
3369
|
+
status: "active" | "canceled" | "past_due" | "trialing" | "unpaid" | "incomplete";
|
|
3370
|
+
priceId: string;
|
|
3371
|
+
currentPeriodEnd: Date;
|
|
3372
|
+
cancelAtPeriodEnd: boolean;
|
|
3373
|
+
}
|
|
3374
|
+
interface CheckoutOptions {
|
|
3375
|
+
successUrl?: string;
|
|
3376
|
+
cancelUrl?: string;
|
|
3377
|
+
trialDays?: number;
|
|
3378
|
+
metadata?: Record<string, string>;
|
|
3379
|
+
}
|
|
3380
|
+
interface StripeModule {
|
|
3381
|
+
/** Create a Stripe customer for a user and persist the customer ID */
|
|
3382
|
+
createCustomer(userId: string, email: string, name?: string): Promise<string>;
|
|
3383
|
+
/** Get the Stripe customer ID stored for a user */
|
|
3384
|
+
getCustomerId(userId: string): Promise<string | null>;
|
|
3385
|
+
/** Create a Stripe Checkout Session and return its URL + ID */
|
|
3386
|
+
createCheckoutSession(userId: string, priceId: string, options?: CheckoutOptions): Promise<{
|
|
3387
|
+
url: string;
|
|
3388
|
+
sessionId: string;
|
|
3389
|
+
}>;
|
|
3390
|
+
/** Create a Stripe Billing Portal session and return its URL */
|
|
3391
|
+
createPortalSession(userId: string, returnUrl: string): Promise<{
|
|
3392
|
+
url: string;
|
|
3393
|
+
}>;
|
|
3394
|
+
/** Return current subscription info for a user from the database */
|
|
3395
|
+
getSubscription(userId: string): Promise<SubscriptionInfo | null>;
|
|
3396
|
+
/** Verify Stripe webhook signature and dispatch to internal handlers */
|
|
3397
|
+
handleWebhook(request: Request): Promise<Response>;
|
|
3398
|
+
/** Route an incoming HTTP request to the appropriate handler (returns null if path unmatched) */
|
|
3399
|
+
handleRequest(request: Request): Promise<Response | null>;
|
|
3400
|
+
}
|
|
3401
|
+
declare function createStripeModule(config: StripeConfig, db: Database): StripeModule;
|
|
3402
|
+
|
|
3403
|
+
declare function stripe(config: StripeConfig): KavachPlugin;
|
|
3404
|
+
|
|
3405
|
+
type TwoFactorConfig = TotpConfig;
|
|
3406
|
+
declare function twoFactor(config?: TwoFactorConfig): KavachPlugin;
|
|
3407
|
+
|
|
3408
|
+
/**
|
|
3409
|
+
* Trusted device windows for 2FA in TheAuth.
|
|
3410
|
+
*
|
|
3411
|
+
* After a successful 2FA challenge, a device can be marked as trusted.
|
|
3412
|
+
* Subsequent logins from the same device skip 2FA until the trust window
|
|
3413
|
+
* expires (default 30 days) or trust is explicitly revoked.
|
|
3414
|
+
*
|
|
3415
|
+
* Fingerprints are HMAC-signed to prevent client-side spoofing.
|
|
3416
|
+
*/
|
|
3417
|
+
|
|
3418
|
+
interface TrustedDeviceConfig {
|
|
3419
|
+
/** How long to trust a device in seconds (default: 30 days). */
|
|
3420
|
+
trustDurationSeconds?: number;
|
|
3421
|
+
/** Maximum number of trusted devices per user (default: 10). */
|
|
3422
|
+
maxDevices?: number;
|
|
3423
|
+
/**
|
|
3424
|
+
* Secret used to HMAC-sign fingerprints, preventing client spoofing.
|
|
3425
|
+
* Should be a stable application secret (e.g. from env). If omitted a
|
|
3426
|
+
* random per-process key is used (fingerprints won't survive restarts).
|
|
3427
|
+
*/
|
|
3428
|
+
secret?: string;
|
|
3429
|
+
}
|
|
3430
|
+
interface TrustedDevice {
|
|
3431
|
+
id: string;
|
|
3432
|
+
fingerprint: string;
|
|
3433
|
+
label: string;
|
|
3434
|
+
trustedAt: Date;
|
|
3435
|
+
expiresAt: Date;
|
|
3436
|
+
}
|
|
3437
|
+
interface TrustedDeviceModule {
|
|
3438
|
+
/**
|
|
3439
|
+
* Mark the device identified by `deviceFingerprint` as trusted for
|
|
3440
|
+
* `userId`. Returns a stable trust token (the record id) that can be
|
|
3441
|
+
* stored in a long-lived cookie.
|
|
3442
|
+
*/
|
|
3443
|
+
trustDevice(userId: string, deviceFingerprint: string): Promise<string>;
|
|
3444
|
+
/** Returns true if the device is currently trusted (not expired). */
|
|
3445
|
+
isTrusted(userId: string, deviceFingerprint: string): Promise<boolean>;
|
|
3446
|
+
/** Remove trust for a single device. */
|
|
3447
|
+
revokeDevice(userId: string, deviceFingerprint: string): Promise<void>;
|
|
3448
|
+
/** Remove trust for every device belonging to a user. */
|
|
3449
|
+
revokeAllDevices(userId: string): Promise<void>;
|
|
3450
|
+
/** List all active trusted devices for a user. */
|
|
3451
|
+
listDevices(userId: string): Promise<TrustedDevice[]>;
|
|
3452
|
+
/**
|
|
3453
|
+
* Derive a stable, HMAC-protected fingerprint from request headers.
|
|
3454
|
+
* The same request will always produce the same fingerprint; changing
|
|
3455
|
+
* user-agent or accept-language invalidates the fingerprint.
|
|
3456
|
+
*/
|
|
3457
|
+
generateFingerprint(request: Request): Promise<string>;
|
|
3458
|
+
}
|
|
3459
|
+
declare function createTrustedDeviceModule(config: TrustedDeviceConfig, db: Database): TrustedDeviceModule;
|
|
3460
|
+
/**
|
|
3461
|
+
* Derive a human-readable device label from a request's User-Agent header.
|
|
3462
|
+
* Useful when calling `trustDevice` so the stored label is descriptive.
|
|
3463
|
+
*/
|
|
3464
|
+
declare function deviceLabelFromRequest(request: Request): string;
|
|
3465
|
+
|
|
3466
|
+
export { type AccessTokenClaims, type AdditionalFieldsConfig, type AdditionalFieldsModule, AdminConfig, type AnonymousAuthConfig, type AnonymousAuthModule, ApiKeyManagerConfig, AuthAdapter, type AuthorizeParams, type BearerAuthOptions, type BudgetCheckResult, type CheckParams, type CheckResult, type CheckoutOptions, type CostAlert, type CostAttributionConfig, type CostAttributionModule, type CostReport, type CreateEphemeralSessionInput, type CustomSessionConfig, type CustomSessionModule, DEFAULT_ATLASSIAN_SCOPES, DEFAULT_DISCORD_SCOPES, DEFAULT_DROPBOX_SCOPES, DEFAULT_FIGMA_SCOPES, DEFAULT_NOTION_SCOPES, DEFAULT_REDDIT_SCOPES, DEFAULT_SLACK_SCOPES, DEFAULT_SPOTIFY_SCOPES, DEFAULT_TWITCH_SCOPES, DEFAULT_ZOOM_SCOPES, type DeleteOptions, type DeleteResult, type DeviceAuthConfig, type DeviceAuthModule, type DeviceAuthStatus, type DeviceCodeResponse, EVENT_TYPES, EmailOtpConfig, type EndpointGroup, type EndpointLimit, type EphemeralSession, type EphemeralSessionConfig, type EphemeralSessionModule, type EphemeralSessionValidateResult, type EventStreamConfig, type EventStreamModule, type EventType, type ExpandParams, type FederatedAgent, type FederationConfig, type FederationModule, type FederationToken, type FederationWellKnown, type FieldDefinition, type GdprModule, type GenericOIDCConfig, type GetUserClaimsFn, type GoogleUser, type HeaderAuthOptions, HibpApiError, HibpBreachedError, type HibpConfig, type HibpModule, type InstanceIdentity, type IssueFederationTokenInput, type JsonWebKeySet, type JwtSessionConfig, type JwtSessionModule, type KVNamespace, KVStore, type LastLoginConfig, type LastLoginModule, type ListObjectsParams, type ListSubjectsParams, type LoginEvent, type LoginMethod, MagicLinkConfig, MemoryStore, type OAuthAccount, type OAuthCallbackResult, type OAuthModule, type OAuthModuleConfig, type OAuthPluginConfig, type OAuthProvider, type OAuthProviderConfig, type OAuthProxyConfig, OAuthProxyError, type OAuthProxyModule, type OAuthProxyPluginConfig, type OAuthTokens, type OAuthUserInfo, type OidcClient, type OidcDiscoveryDocument, type OidcProviderConfig, type OidcProviderModule, type OneTapConfig, type OneTapModule, OneTapVerifyError, type OpenApiComponents, type OpenApiConfig, type OpenApiDocument, type OpenApiInfo, type OpenApiMediaType, type OpenApiModule, type OpenApiOperation, type OpenApiParameter, type OpenApiPathItem, type OpenApiRequestBody, type OpenApiResponse, type OpenApiSchema, type OpenApiSecurityRequirement, type OpenApiSecurityScheme, type OpenApiServer, OrgConfig, PasskeyConfig, type PermissionRuleSet, type PolarConfig, type PolarModule, type PolarSubscription, type ProxyTokens, type RateLimitConfig, type RateLimitMiddlewareOptions, type RateLimitConfig$1 as RateLimitPluginConfig, type RateLimitResult, type RateLimitStore, type RateLimiter, type ReBACConfig, type ReBACModule, type RecordCostInput, type RecordLoginInput, type RegisterClientInput, type Relationship, ResolvedUser, type ResourceNode, type ScimConfig, type ScimGroup, type ScimModule, type ScimUser, type SessionTokens, type SessionUser, type SiweConfig, type SiweModule, type SiweVerifyResult, type StreamEvent, type StripeConfig, type StripeModule, type SubscriptionInfo, type TokenParams, type TokenResponse, TotpConfig, type TrustLevel, type TrustedDevice, type TrustedDeviceConfig, type TrustedDeviceModule, type TrustedInstance, type TwoFactorConfig, type UserDataExport, type UserInfoClaims, type ValidationResult, type VerifiedSession, additionalFields, admin, anonymousAuth, apiKeys, atlassianProvider, auth0Provider, bearerAuth, bitbucketProvider, cognitoProvider, coinbaseProvider, createAdditionalFieldsModule, createAnonymousAuthModule, createAppleProvider, createAtlassianProvider, createCostAttributionModule, createCustomSessionModule, createDeviceAuthModule, createDiscordProvider, createDropboxProvider, createEphemeralSessionModule, createEventStreamModule, createFederationModule, createFigmaProvider, createGdprModule, createGithubProvider, createGitlabProvider, createGoogleProvider, createHibpModule, createJwtSessionModule, createLastLoginModule, createLinkedInProvider, createMicrosoftProvider, createNotionProvider, createOAuthModule, createOAuthProxyModule, createOidcProviderModule, createOneTapModule, createOpenApiModule, createPolarModule, createRateLimiter, createReBACModule, createRedditProvider, createScimModule, createSiweModule, createSlackProvider, createSpotifyProvider, createStripeModule, createTrustedDeviceModule, createTwitchProvider, createTwitterProvider, createZoomProvider, customAuth, customSession, deviceAuth, deviceLabelFromRequest, dropboxProvider, emailOtp, facebookProvider, figmaProvider, gdpr, genericOIDC, headerAuth, huggingfaceProvider, kakaoProvider, kickProvider, kvStore, lineProvider, linearProvider, magicLink, naverProvider, normalizeProfile$9 as normalizeAtlassianProfile, normalizeProfile$8 as normalizeDiscordProfile, normalizeProfile$7 as normalizeDropboxProfile, normalizeProfile$6 as normalizeFigmaProfile, normalizeProfile$5 as normalizeNotionProfile, normalizeProfile$4 as normalizeRedditProfile, normalizeProfile$3 as normalizeSlackProfile, normalizeProfile$2 as normalizeSpotifyProfile, normalizeProfile$1 as normalizeTwitchProfile, normalizeProfile as normalizeZoomProfile, notionProvider, oauth, oauthProxy, oktaProvider, oneTap, organization, passkey, paypalProvider, polar, polarProvider, railwayProvider, rateLimit, redditProvider, robloxProvider, salesforceProvider, scim, siwe, spotifyProvider, stripe, tiktokProvider, twitchProvider, twoFactor, vercelProvider, vkProvider, wechatProvider, withRateLimit, yahooProvider, zoomProvider };
|