@oglofus/auth 1.1.0 → 1.1.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/README.md +13 -8
- package/dist/core/auth.d.ts +3 -3
- package/dist/core/auth.js +22 -4
- package/dist/core/validators.js +1 -1
- package/dist/errors/index.d.ts +2 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/plugins/email-otp.d.ts +1 -1
- package/dist/plugins/email-otp.js +6 -17
- package/dist/plugins/index.d.ts +3 -2
- package/dist/plugins/index.js +3 -2
- package/dist/plugins/magic-link.d.ts +1 -1
- package/dist/plugins/magic-link.js +4 -10
- package/dist/plugins/oauth2.d.ts +2 -2
- package/dist/plugins/oauth2.js +8 -4
- package/dist/plugins/organizations.d.ts +2 -2
- package/dist/plugins/organizations.js +21 -4
- package/dist/plugins/passkey.d.ts +1 -1
- package/dist/plugins/passkey.js +3 -6
- package/dist/plugins/password.d.ts +1 -1
- package/dist/plugins/password.js +3 -6
- package/dist/plugins/stripe.d.ts +25 -0
- package/dist/plugins/stripe.js +583 -0
- package/dist/plugins/two-factor.d.ts +1 -1
- package/dist/plugins/two-factor.js +2 -2
- package/dist/types/adapters.d.ts +32 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -2
- package/dist/types/model.d.ts +71 -1
- package/dist/types/plugins.d.ts +100 -25
- package/dist/types/results.d.ts +1 -1
- package/dist/types/results.js +11 -2
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Optional for app-level integrations:
|
|
|
29
29
|
|
|
30
30
|
- `arctic` for OAuth providers in your app code.
|
|
31
31
|
- `@oslojs/otp` if you need direct OTP utilities in your app (the library already uses it internally for TOTP).
|
|
32
|
+
- `stripe` if you use the Stripe billing plugin.
|
|
32
33
|
|
|
33
34
|
## Quick Start (Password)
|
|
34
35
|
|
|
@@ -127,15 +128,9 @@ You can build issues with helpers:
|
|
|
127
128
|
```ts
|
|
128
129
|
import { createIssue, createIssueFactory } from "@oglofus/auth";
|
|
129
130
|
|
|
130
|
-
const issue = createIssueFactory<{ email: string; profile: unknown }>([
|
|
131
|
-
"email",
|
|
132
|
-
"profile",
|
|
133
|
-
] as const);
|
|
131
|
+
const issue = createIssueFactory<{ email: string; profile: unknown }>(["email", "profile"] as const);
|
|
134
132
|
issue.email("Email is required");
|
|
135
|
-
issue.$path(
|
|
136
|
-
["profile", { key: "addresses" }, { index: 0 }, "city"],
|
|
137
|
-
"City is required",
|
|
138
|
-
);
|
|
133
|
+
issue.$path(["profile", { key: "addresses" }, { index: 0 }, "city"], "City is required");
|
|
139
134
|
createIssue("Generic failure");
|
|
140
135
|
```
|
|
141
136
|
|
|
@@ -240,6 +235,14 @@ const result = await auth.authenticate({
|
|
|
240
235
|
- Multi-tenant orgs, memberships, role inheritance, feature/limit entitlements, invites.
|
|
241
236
|
- Validates role topology on startup (default role, owner role presence, inheritance cycles).
|
|
242
237
|
|
|
238
|
+
### Stripe (Domain Plugin)
|
|
239
|
+
|
|
240
|
+
- Method: `"stripe"`
|
|
241
|
+
- User and organization subscriptions with typed billing subjects.
|
|
242
|
+
- Checkout session creation, billing portal sessions, webhook verification, local subscription snapshots.
|
|
243
|
+
- Plan-level features and limits, trial tracking, and organization entitlement merge support.
|
|
244
|
+
- Requires the `stripe` package in your application.
|
|
245
|
+
|
|
243
246
|
## Account Discovery
|
|
244
247
|
|
|
245
248
|
Use `discover(...)` to support login/register routing logic before full auth:
|
|
@@ -257,6 +260,8 @@ See ready-to-copy integrations:
|
|
|
257
260
|
- [`examples/sveltekit-email-otp`](./examples/sveltekit-email-otp)
|
|
258
261
|
- [`examples/oauth2-google-arctic`](./examples/oauth2-google-arctic)
|
|
259
262
|
- [`examples/two-factor-totp-oslo`](./examples/two-factor-totp-oslo)
|
|
263
|
+
- [`examples/stripe-user-billing`](./examples/stripe-user-billing)
|
|
264
|
+
- [`examples/stripe-organization-billing`](./examples/stripe-organization-billing)
|
|
260
265
|
|
|
261
266
|
## Scripts
|
|
262
267
|
|
package/dist/core/auth.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type AuthResult, type OperationResult } from "../types/results.js";
|
|
2
|
-
import type { AuthConfig, AuthenticateInputFromPlugins, AuthPublicApi, AnyPlugin, PluginApiMap, PluginMethodsWithApi, RegisterInputFromPlugins } from "../types/plugins.js";
|
|
3
1
|
import type { AuthRequestContext, CompleteProfileInput, DiscoverAccountDecision, DiscoverAccountInput, TwoFactorVerifyInput, UserBase } from "../types/model.js";
|
|
2
|
+
import type { AnyPlugin, AuthConfig, AuthPublicApi, AuthenticateInputFromPlugins, PluginApiForMethod, PluginMethodsWithApi, RegisterInputFromPlugins } from "../types/plugins.js";
|
|
3
|
+
import { type AuthResult, type OperationResult } from "../types/results.js";
|
|
4
4
|
export declare class OglofusAuth<U extends UserBase, P extends readonly AnyPlugin<U>[]> implements AuthPublicApi<U, P> {
|
|
5
5
|
private readonly config;
|
|
6
6
|
private readonly pluginMap;
|
|
@@ -10,7 +10,7 @@ export declare class OglofusAuth<U extends UserBase, P extends readonly AnyPlugi
|
|
|
10
10
|
discover(input: DiscoverAccountInput, request?: AuthRequestContext): Promise<OperationResult<DiscoverAccountDecision>>;
|
|
11
11
|
authenticate(input: AuthenticateInputFromPlugins<P>, request?: AuthRequestContext): Promise<AuthResult<U>>;
|
|
12
12
|
register(input: RegisterInputFromPlugins<P>, request?: AuthRequestContext): Promise<AuthResult<U>>;
|
|
13
|
-
method<M extends PluginMethodsWithApi<P>>(method: M):
|
|
13
|
+
method<M extends PluginMethodsWithApi<P>>(method: M): PluginApiForMethod<P, M>;
|
|
14
14
|
verifySecondFactor(input: TwoFactorVerifyInput, request?: AuthRequestContext): Promise<AuthResult<U>>;
|
|
15
15
|
completeProfile(input: CompleteProfileInput<U>, request?: AuthRequestContext): Promise<AuthResult<U>>;
|
|
16
16
|
validateSession(sessionId: string, _request?: AuthRequestContext): Promise<{
|
package/dist/core/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AuthError } from "../errors/index.js";
|
|
2
|
+
import { createIssue } from "../issues/index.js";
|
|
2
3
|
import { errorOperation, errorResult, successOperation, successResult, } from "../types/results.js";
|
|
3
4
|
import { DEFAULT_SESSION_TTL_SECONDS, addSeconds, createId, deterministicTokenHash, normalizeEmailDefault, now, } from "./utils.js";
|
|
4
|
-
import { createIssue } from "../issues/index.js";
|
|
5
5
|
const hasEmail = (value) => typeof value === "object" && value !== null && "email" in value && typeof value.email === "string";
|
|
6
6
|
const toAuthError = (error) => {
|
|
7
7
|
if (error instanceof AuthError) {
|
|
@@ -26,6 +26,12 @@ export class OglofusAuth {
|
|
|
26
26
|
this.config = config;
|
|
27
27
|
this.normalizeEmail = config.normalize?.email ?? normalizeEmailDefault;
|
|
28
28
|
this.validatePlugins();
|
|
29
|
+
const getPluginApi = (method) => {
|
|
30
|
+
if (!this.apiMap.has(method)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return this.apiMap.get(method);
|
|
34
|
+
};
|
|
29
35
|
for (const plugin of config.plugins) {
|
|
30
36
|
this.pluginMap.set(plugin.method, plugin);
|
|
31
37
|
if (plugin.createApi) {
|
|
@@ -33,6 +39,7 @@ export class OglofusAuth {
|
|
|
33
39
|
adapters: this.config.adapters,
|
|
34
40
|
now,
|
|
35
41
|
security: this.config.security,
|
|
42
|
+
getPluginApi,
|
|
36
43
|
}));
|
|
37
44
|
}
|
|
38
45
|
}
|
|
@@ -410,6 +417,12 @@ export class OglofusAuth {
|
|
|
410
417
|
now,
|
|
411
418
|
security: this.config.security,
|
|
412
419
|
request,
|
|
420
|
+
getPluginApi: (method) => {
|
|
421
|
+
if (!this.apiMap.has(method)) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
return this.apiMap.get(method);
|
|
425
|
+
},
|
|
413
426
|
};
|
|
414
427
|
}
|
|
415
428
|
async createSession(userId) {
|
|
@@ -453,9 +466,7 @@ export class OglofusAuth {
|
|
|
453
466
|
if (result.allowed) {
|
|
454
467
|
return null;
|
|
455
468
|
}
|
|
456
|
-
return new AuthError("RATE_LIMITED", "Too many requests.", 429, [], result.retryAfterSeconds === undefined
|
|
457
|
-
? undefined
|
|
458
|
-
: { retryAfterSeconds: result.retryAfterSeconds });
|
|
469
|
+
return new AuthError("RATE_LIMITED", "Too many requests.", 429, [], result.retryAfterSeconds === undefined ? undefined : { retryAfterSeconds: result.retryAfterSeconds });
|
|
459
470
|
}
|
|
460
471
|
makeRateLimitKey(scope, identity, request) {
|
|
461
472
|
if (!request?.ip) {
|
|
@@ -521,6 +532,7 @@ export class OglofusAuth {
|
|
|
521
532
|
const methods = new Set();
|
|
522
533
|
let twoFactorCount = 0;
|
|
523
534
|
let organizationsCount = 0;
|
|
535
|
+
let stripeCount = 0;
|
|
524
536
|
for (const plugin of this.config.plugins) {
|
|
525
537
|
if (methods.has(plugin.method)) {
|
|
526
538
|
throw new AuthError("PLUGIN_METHOD_CONFLICT", `Plugin method conflict for '${plugin.method}'.`, 500);
|
|
@@ -532,6 +544,9 @@ export class OglofusAuth {
|
|
|
532
544
|
if (plugin.method === "organizations") {
|
|
533
545
|
organizationsCount += 1;
|
|
534
546
|
}
|
|
547
|
+
if (plugin.method === "stripe") {
|
|
548
|
+
stripeCount += 1;
|
|
549
|
+
}
|
|
535
550
|
if (plugin.kind === "auth_method") {
|
|
536
551
|
if (plugin.supports.register && !plugin.register) {
|
|
537
552
|
throw new AuthError("PLUGIN_MISCONFIGURED", `Plugin '${plugin.method}' declares register support but no register handler.`, 500);
|
|
@@ -553,6 +568,9 @@ export class OglofusAuth {
|
|
|
553
568
|
if (organizationsCount > 1) {
|
|
554
569
|
throw new AuthError("PLUGIN_MISCONFIGURED", "At most one organizations plugin is allowed.", 500);
|
|
555
570
|
}
|
|
571
|
+
if (stripeCount > 1) {
|
|
572
|
+
throw new AuthError("PLUGIN_MISCONFIGURED", "At most one stripe plugin is allowed.", 500);
|
|
573
|
+
}
|
|
556
574
|
if ((this.config.accountDiscovery?.mode ?? "private") === "explicit" && !this.config.adapters.identity) {
|
|
557
575
|
throw new AuthError("PLUGIN_MISCONFIGURED", "identity adapter is required when accountDiscovery.mode is explicit.", 500);
|
|
558
576
|
}
|
package/dist/core/validators.js
CHANGED
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { UserBase } from "../types/model.js";
|
|
2
|
-
import type { ProfileCompletionState, TwoFactorRequiredMeta } from "../types/model.js";
|
|
3
1
|
import type { Issue } from "../issues/index.js";
|
|
4
|
-
|
|
2
|
+
import type { ProfileCompletionState, TwoFactorRequiredMeta, UserBase } from "../types/model.js";
|
|
3
|
+
export type AuthErrorCode = "INVALID_INPUT" | "METHOD_DISABLED" | "METHOD_NOT_REGISTERABLE" | "ACCOUNT_NOT_FOUND" | "ACCOUNT_EXISTS" | "ACCOUNT_EXISTS_WITH_DIFFERENT_METHOD" | "ORGANIZATION_NOT_FOUND" | "MEMBERSHIP_NOT_FOUND" | "MEMBERSHIP_FORBIDDEN" | "ROLE_INVALID" | "ROLE_NOT_ASSIGNABLE" | "ORGANIZATION_INVITE_INVALID" | "ORGANIZATION_INVITE_EXPIRED" | "SEAT_LIMIT_REACHED" | "FEATURE_DISABLED" | "LIMIT_EXCEEDED" | "LAST_OWNER_GUARD" | "SESSION_NOT_FOUND" | "INVALID_CREDENTIALS" | "USER_NOT_FOUND" | "CONFLICT" | "RATE_LIMITED" | "DELIVERY_FAILED" | "EMAIL_NOT_VERIFIED" | "OTP_EXPIRED" | "OTP_INVALID" | "MAGIC_LINK_EXPIRED" | "MAGIC_LINK_INVALID" | "OAUTH2_PROVIDER_DISABLED" | "OAUTH2_EXCHANGE_FAILED" | "PASSKEY_INVALID_ASSERTION" | "PASSKEY_INVALID_ATTESTATION" | "PASSKEY_CHALLENGE_EXPIRED" | "CUSTOMER_NOT_FOUND" | "SUBSCRIPTION_NOT_FOUND" | "SUBSCRIPTION_ALREADY_EXISTS" | "TRIAL_NOT_AVAILABLE" | "STRIPE_WEBHOOK_INVALID" | "PROFILE_COMPLETION_REQUIRED" | "PROFILE_COMPLETION_EXPIRED" | "TWO_FACTOR_REQUIRED" | "TWO_FACTOR_INVALID" | "TWO_FACTOR_EXPIRED" | "RECOVERY_CODE_INVALID" | "PLUGIN_METHOD_CONFLICT" | "PLUGIN_MISCONFIGURED" | "INTERNAL_ERROR";
|
|
5
4
|
export declare class AuthError extends Error {
|
|
6
5
|
readonly code: AuthErrorCode;
|
|
7
6
|
readonly status: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from "./types/index.js";
|
|
2
|
-
export * from "./issues/index.js";
|
|
3
|
-
export * from "./errors/index.js";
|
|
4
|
-
export * from "./core/events.js";
|
|
5
1
|
export * from "./core/auth.js";
|
|
2
|
+
export * from "./core/events.js";
|
|
3
|
+
export * from "./errors/index.js";
|
|
4
|
+
export * from "./issues/index.js";
|
|
6
5
|
export * from "./plugins/index.js";
|
|
6
|
+
export * from "./types/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from "./types/index.js";
|
|
2
|
-
export * from "./issues/index.js";
|
|
3
|
-
export * from "./errors/index.js";
|
|
4
|
-
export * from "./core/events.js";
|
|
5
1
|
export * from "./core/auth.js";
|
|
2
|
+
export * from "./core/events.js";
|
|
3
|
+
export * from "./errors/index.js";
|
|
4
|
+
export * from "./issues/index.js";
|
|
6
5
|
export * from "./plugins/index.js";
|
|
6
|
+
export * from "./types/index.js";
|
|
@@ -9,4 +9,4 @@ export type EmailOtpPluginConfig<U extends UserBase, K extends keyof U> = {
|
|
|
9
9
|
maxAttempts?: number;
|
|
10
10
|
codeLength?: number;
|
|
11
11
|
};
|
|
12
|
-
export declare const emailOtpPlugin: <U extends UserBase, K extends keyof U>(config: EmailOtpPluginConfig<U, K>) => AuthMethodPlugin<"email_otp", EmailOtpRegisterInput<U, K>, EmailOtpAuthenticateInput, U, EmailOtpPluginApi>;
|
|
12
|
+
export declare const emailOtpPlugin: <U extends UserBase, K extends keyof U>(config: EmailOtpPluginConfig<U, K>) => AuthMethodPlugin<"email_otp", EmailOtpRegisterInput<U, K>, EmailOtpAuthenticateInput, U, EmailOtpPluginApi, true, true>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { addSeconds, cloneWithout, createId, createNumericCode, secretHash, secretVerify } from "../core/utils.js";
|
|
2
|
+
import { ensureFields } from "../core/validators.js";
|
|
1
3
|
import { AuthError } from "../errors/index.js";
|
|
2
4
|
import { createIssue } from "../issues/index.js";
|
|
3
5
|
import { errorOperation, successOperation } from "../types/results.js";
|
|
4
|
-
import { addSeconds, createId, createNumericCode, secretHash, secretVerify, } from "../core/utils.js";
|
|
5
|
-
import { cloneWithout } from "../core/utils.js";
|
|
6
|
-
import { ensureFields } from "../core/validators.js";
|
|
7
6
|
const PENDING_PREFIX = "pending:";
|
|
8
7
|
const EMAIL_OTP_REQUEST_POLICY = { limit: 3, windowSeconds: 300 };
|
|
9
8
|
const OTP_VERIFY_POLICY = { limit: 10, windowSeconds: 300 };
|
|
@@ -38,9 +37,7 @@ export const emailOtpPlugin = (config) => {
|
|
|
38
37
|
if (challenge.expiresAt.getTime() <= now.getTime()) {
|
|
39
38
|
return {
|
|
40
39
|
ok: false,
|
|
41
|
-
error: new AuthError("OTP_EXPIRED", "OTP has expired.", 400, [
|
|
42
|
-
createIssue("OTP expired", ["code"]),
|
|
43
|
-
]),
|
|
40
|
+
error: new AuthError("OTP_EXPIRED", "OTP has expired.", 400, [createIssue("OTP expired", ["code"])]),
|
|
44
41
|
};
|
|
45
42
|
}
|
|
46
43
|
if (challenge.attempts >= maxAttempts) {
|
|
@@ -60,9 +57,7 @@ export const emailOtpPlugin = (config) => {
|
|
|
60
57
|
}
|
|
61
58
|
return {
|
|
62
59
|
ok: false,
|
|
63
|
-
error: new AuthError("OTP_INVALID", "Invalid OTP code.", 400, [
|
|
64
|
-
createIssue("Invalid code", ["code"]),
|
|
65
|
-
]),
|
|
60
|
+
error: new AuthError("OTP_INVALID", "Invalid OTP code.", 400, [createIssue("Invalid code", ["code"])]),
|
|
66
61
|
};
|
|
67
62
|
}
|
|
68
63
|
const consumed = await config.otp.consumeChallenge(challengeId);
|
|
@@ -96,9 +91,7 @@ export const emailOtpPlugin = (config) => {
|
|
|
96
91
|
const email = input.email.trim().toLowerCase();
|
|
97
92
|
if (ctx.adapters.rateLimiter) {
|
|
98
93
|
const policy = ctx.security?.rateLimits?.emailOtpRequest ?? EMAIL_OTP_REQUEST_POLICY;
|
|
99
|
-
const limited = await ctx.adapters.rateLimiter.consume(request?.ip
|
|
100
|
-
? `emailOtpRequest:ip:${request.ip}:identity:${email}`
|
|
101
|
-
: `emailOtpRequest:identity:${email}`, policy.limit, policy.windowSeconds);
|
|
94
|
+
const limited = await ctx.adapters.rateLimiter.consume(request?.ip ? `emailOtpRequest:ip:${request.ip}:identity:${email}` : `emailOtpRequest:identity:${email}`, policy.limit, policy.windowSeconds);
|
|
102
95
|
if (!limited.allowed) {
|
|
103
96
|
return errorOperation(createRateLimitedError(limited.retryAfterSeconds));
|
|
104
97
|
}
|
|
@@ -154,11 +147,7 @@ export const emailOtpPlugin = (config) => {
|
|
|
154
147
|
if (requiredError) {
|
|
155
148
|
return errorOperation(requiredError);
|
|
156
149
|
}
|
|
157
|
-
const payload = cloneWithout(input, [
|
|
158
|
-
"method",
|
|
159
|
-
"challengeId",
|
|
160
|
-
"code",
|
|
161
|
-
]);
|
|
150
|
+
const payload = cloneWithout(input, ["method", "challengeId", "code"]);
|
|
162
151
|
payload.email = verified.email;
|
|
163
152
|
payload.emailVerified = true;
|
|
164
153
|
const user = await ctx.adapters.users.create(payload);
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export * from "./password.js";
|
|
2
1
|
export * from "./email-otp.js";
|
|
3
2
|
export * from "./magic-link.js";
|
|
4
3
|
export * from "./oauth2.js";
|
|
4
|
+
export * from "./organizations.js";
|
|
5
5
|
export * from "./passkey.js";
|
|
6
|
+
export * from "./password.js";
|
|
7
|
+
export * from "./stripe.js";
|
|
6
8
|
export * from "./two-factor.js";
|
|
7
|
-
export * from "./organizations.js";
|
package/dist/plugins/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export * from "./password.js";
|
|
2
1
|
export * from "./email-otp.js";
|
|
3
2
|
export * from "./magic-link.js";
|
|
4
3
|
export * from "./oauth2.js";
|
|
4
|
+
export * from "./organizations.js";
|
|
5
5
|
export * from "./passkey.js";
|
|
6
|
+
export * from "./password.js";
|
|
7
|
+
export * from "./stripe.js";
|
|
6
8
|
export * from "./two-factor.js";
|
|
7
|
-
export * from "./organizations.js";
|
|
@@ -8,4 +8,4 @@ export type MagicLinkPluginConfig<U extends UserBase, K extends keyof U> = {
|
|
|
8
8
|
tokenTtlSeconds?: number;
|
|
9
9
|
baseVerifyUrl: string;
|
|
10
10
|
};
|
|
11
|
-
export declare const magicLinkPlugin: <U extends UserBase, K extends keyof U>(config: MagicLinkPluginConfig<U, K>) => AuthMethodPlugin<"magic_link", MagicLinkRegisterInput<U, K>, MagicLinkAuthenticateInput, U, MagicLinkPluginApi>;
|
|
11
|
+
export declare const magicLinkPlugin: <U extends UserBase, K extends keyof U>(config: MagicLinkPluginConfig<U, K>) => AuthMethodPlugin<"magic_link", MagicLinkRegisterInput<U, K>, MagicLinkAuthenticateInput, U, MagicLinkPluginApi, true, true>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { addSeconds, cloneWithout, createId, createToken, deterministicTokenHash } from "../core/utils.js";
|
|
2
|
+
import { ensureFields } from "../core/validators.js";
|
|
1
3
|
import { AuthError } from "../errors/index.js";
|
|
2
4
|
import { createIssue } from "../issues/index.js";
|
|
3
5
|
import { errorOperation, successOperation } from "../types/results.js";
|
|
4
|
-
import { addSeconds, createId, createToken, deterministicTokenHash } from "../core/utils.js";
|
|
5
|
-
import { cloneWithout } from "../core/utils.js";
|
|
6
|
-
import { ensureFields } from "../core/validators.js";
|
|
7
6
|
const MAGIC_LINK_REQUEST_POLICY = { limit: 3, windowSeconds: 300 };
|
|
8
7
|
const createRateLimitedError = (retryAfterSeconds) => new AuthError("RATE_LIMITED", "Too many requests.", 429, [], retryAfterSeconds === undefined ? undefined : { retryAfterSeconds });
|
|
9
8
|
export const magicLinkPlugin = (config) => {
|
|
@@ -56,9 +55,7 @@ export const magicLinkPlugin = (config) => {
|
|
|
56
55
|
const email = input.email.trim().toLowerCase();
|
|
57
56
|
if (ctx.adapters.rateLimiter) {
|
|
58
57
|
const policy = ctx.security?.rateLimits?.magicLinkRequest ?? MAGIC_LINK_REQUEST_POLICY;
|
|
59
|
-
const limited = await ctx.adapters.rateLimiter.consume(request?.ip
|
|
60
|
-
? `magicLinkRequest:ip:${request.ip}:identity:${email}`
|
|
61
|
-
: `magicLinkRequest:identity:${email}`, policy.limit, policy.windowSeconds);
|
|
58
|
+
const limited = await ctx.adapters.rateLimiter.consume(request?.ip ? `magicLinkRequest:ip:${request.ip}:identity:${email}` : `magicLinkRequest:identity:${email}`, policy.limit, policy.windowSeconds);
|
|
62
59
|
if (!limited.allowed) {
|
|
63
60
|
return errorOperation(createRateLimitedError(limited.retryAfterSeconds));
|
|
64
61
|
}
|
|
@@ -115,10 +112,7 @@ export const magicLinkPlugin = (config) => {
|
|
|
115
112
|
if (requiredError) {
|
|
116
113
|
return errorOperation(requiredError);
|
|
117
114
|
}
|
|
118
|
-
const payload = cloneWithout(input, [
|
|
119
|
-
"method",
|
|
120
|
-
"token",
|
|
121
|
-
]);
|
|
115
|
+
const payload = cloneWithout(input, ["method", "token"]);
|
|
122
116
|
payload.email = verified.email;
|
|
123
117
|
payload.emailVerified = true;
|
|
124
118
|
const user = await ctx.adapters.users.create(payload);
|
package/dist/plugins/oauth2.d.ts
CHANGED
|
@@ -38,11 +38,11 @@ export type OAuth2ProviderConfig<U extends UserBase, P extends string> = {
|
|
|
38
38
|
};
|
|
39
39
|
export type OAuth2PluginConfig<U extends UserBase, P extends string, K extends keyof U = never> = {
|
|
40
40
|
providers: {
|
|
41
|
-
[Provider in P]
|
|
41
|
+
[Provider in P]?: OAuth2ProviderConfig<U, Provider>;
|
|
42
42
|
};
|
|
43
43
|
accounts: OAuth2AccountAdapter<P>;
|
|
44
44
|
requiredProfileFields?: readonly K[];
|
|
45
45
|
pendingProfileTtlSeconds?: number;
|
|
46
46
|
};
|
|
47
47
|
export declare const arcticAuthorizationCodeExchange: (client: ArcticAuthorizationCodeClient) => OAuth2AuthorizationCodeExchange;
|
|
48
|
-
export declare const oauth2Plugin: <U extends UserBase, P extends string, K extends keyof U = never>(config: OAuth2PluginConfig<U, P, K>) => AuthMethodPlugin<"oauth2", OAuth2AuthenticateInput<P>, OAuth2AuthenticateInput<P>, U>;
|
|
48
|
+
export declare const oauth2Plugin: <U extends UserBase, P extends string, K extends keyof U = never>(config: OAuth2PluginConfig<U, P, K>) => AuthMethodPlugin<"oauth2", OAuth2AuthenticateInput<P>, OAuth2AuthenticateInput<P>, U, never, false, false>;
|
package/dist/plugins/oauth2.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ArcticFetchError, OAuth2RequestError } from "arctic";
|
|
2
|
+
import { addSeconds, createId } from "../core/utils.js";
|
|
3
|
+
import { ensureFields } from "../core/validators.js";
|
|
2
4
|
import { AuthError } from "../errors/index.js";
|
|
3
5
|
import { createIssue } from "../issues/index.js";
|
|
4
6
|
import { errorOperation, successOperation } from "../types/results.js";
|
|
5
|
-
import { addSeconds, createId } from "../core/utils.js";
|
|
6
|
-
import { ensureFields } from "../core/validators.js";
|
|
7
7
|
export const arcticAuthorizationCodeExchange = (client) => {
|
|
8
8
|
return async ({ authorizationCode, codeVerifier }) => {
|
|
9
9
|
const validateAuthorizationCode = client.validateAuthorizationCode;
|
|
@@ -41,7 +41,9 @@ export const oauth2Plugin = (config) => {
|
|
|
41
41
|
},
|
|
42
42
|
completePendingProfile: async (_ctx, { record, user }) => {
|
|
43
43
|
const continuation = record.continuation;
|
|
44
|
-
if (!continuation ||
|
|
44
|
+
if (!continuation ||
|
|
45
|
+
typeof continuation.provider !== "string" ||
|
|
46
|
+
typeof continuation.providerUserId !== "string") {
|
|
45
47
|
return errorOperation(new AuthError("PLUGIN_MISCONFIGURED", "Pending OAuth2 profile is missing provider continuation metadata.", 500));
|
|
46
48
|
}
|
|
47
49
|
await config.accounts.linkAccount({
|
|
@@ -93,7 +95,9 @@ export const oauth2Plugin = (config) => {
|
|
|
93
95
|
]));
|
|
94
96
|
}
|
|
95
97
|
if (error instanceof ArcticFetchError) {
|
|
96
|
-
return errorOperation(new AuthError("OAUTH2_EXCHANGE_FAILED", "OAuth2 provider is unreachable right now.", 502, [
|
|
98
|
+
return errorOperation(new AuthError("OAUTH2_EXCHANGE_FAILED", "OAuth2 provider is unreachable right now.", 502, [
|
|
99
|
+
createIssue("Provider request failed", ["provider"]),
|
|
100
|
+
]));
|
|
97
101
|
}
|
|
98
102
|
return errorOperation(new AuthError("OAUTH2_EXCHANGE_FAILED", "OAuth2 code exchange failed.", 502));
|
|
99
103
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MembershipBase, OrganizationBase, UserBase } from "../types/model.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { DomainPlugin, OrganizationsPluginApi, OrganizationsPluginConfig } from "../types/plugins.js";
|
|
3
3
|
export type OrganizationsPluginOptions<O extends OrganizationBase, Role extends string, M extends MembershipBase<Role>, Permission extends string, Feature extends string, LimitKey extends string, RequiredOrgFields extends keyof O = never> = OrganizationsPluginConfig<O, Role, M, Permission, Feature, LimitKey, RequiredOrgFields> & {
|
|
4
4
|
inviteBaseUrl: string;
|
|
5
5
|
inviteTtlSeconds?: number;
|
|
@@ -9,4 +9,4 @@ export type OrganizationsPluginOptions<O extends OrganizationBase, Role extends
|
|
|
9
9
|
nextRole: Role;
|
|
10
10
|
}) => boolean | Promise<boolean>;
|
|
11
11
|
};
|
|
12
|
-
export declare const organizationsPlugin: <U extends UserBase, O extends OrganizationBase, Role extends string, M extends MembershipBase<Role>, Permission extends string, Feature extends string, LimitKey extends string, RequiredOrgFields extends keyof O = never>(config: OrganizationsPluginOptions<O, Role, M, Permission, Feature, LimitKey, RequiredOrgFields>) => DomainPlugin<"organizations", U, OrganizationsPluginApi<O, Role, M, Permission, Feature, LimitKey, RequiredOrgFields
|
|
12
|
+
export declare const organizationsPlugin: <U extends UserBase, O extends OrganizationBase, Role extends string, M extends MembershipBase<Role>, Permission extends string, Feature extends string, LimitKey extends string, RequiredOrgFields extends keyof O = never>(config: OrganizationsPluginOptions<O, Role, M, Permission, Feature, LimitKey, RequiredOrgFields>) => DomainPlugin<"organizations", U, OrganizationsPluginApi<O, Role, M, Permission, Feature, LimitKey, RequiredOrgFields>, true>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { addSeconds, createId, createToken, deterministicTokenHash } from "../core/utils.js";
|
|
1
2
|
import { AuthError } from "../errors/index.js";
|
|
2
3
|
import { createIssue } from "../issues/index.js";
|
|
3
4
|
import { errorOperation, successOperation } from "../types/results.js";
|
|
4
|
-
import { addSeconds, createId, createToken, deterministicTokenHash } from "../core/utils.js";
|
|
5
5
|
const normalizeEmail = (value) => value.trim().toLowerCase();
|
|
6
6
|
const findOwnerRoles = (roles) => Object.entries(roles)
|
|
7
7
|
.filter(([, definition]) => definition.system?.owner)
|
|
@@ -75,15 +75,34 @@ export const organizationsPlugin = (config) => {
|
|
|
75
75
|
return membershipRes;
|
|
76
76
|
}
|
|
77
77
|
const resolved = resolveRole(membershipRes.data.role, config.handlers.roles);
|
|
78
|
+
const stripeApi = ctx.getPluginApi?.("stripe") ?? null;
|
|
79
|
+
let billingEntitlements = {
|
|
80
|
+
features: {},
|
|
81
|
+
limits: {},
|
|
82
|
+
};
|
|
83
|
+
if (stripeApi) {
|
|
84
|
+
const stripeEntitlements = await stripeApi.getEntitlements({
|
|
85
|
+
subject: {
|
|
86
|
+
kind: "organization",
|
|
87
|
+
organizationId,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
if (!stripeEntitlements.ok) {
|
|
91
|
+
return stripeEntitlements;
|
|
92
|
+
}
|
|
93
|
+
billingEntitlements = stripeEntitlements.data;
|
|
94
|
+
}
|
|
78
95
|
const featureOverrides = await config.handlers.entitlements.getFeatureOverrides(organizationId);
|
|
79
96
|
const limitOverrides = await config.handlers.entitlements.getLimitOverrides(organizationId);
|
|
80
97
|
return successOperation({
|
|
81
98
|
features: {
|
|
82
99
|
...resolved.features,
|
|
100
|
+
...billingEntitlements.features,
|
|
83
101
|
...featureOverrides,
|
|
84
102
|
},
|
|
85
103
|
limits: {
|
|
86
104
|
...resolved.limits,
|
|
105
|
+
...billingEntitlements.limits,
|
|
87
106
|
...limitOverrides,
|
|
88
107
|
},
|
|
89
108
|
});
|
|
@@ -118,9 +137,7 @@ export const organizationsPlugin = (config) => {
|
|
|
118
137
|
});
|
|
119
138
|
return { organization, membership };
|
|
120
139
|
};
|
|
121
|
-
const data = ctx.adapters.withTransaction
|
|
122
|
-
? await ctx.adapters.withTransaction(run)
|
|
123
|
-
: await run();
|
|
140
|
+
const data = ctx.adapters.withTransaction ? await ctx.adapters.withTransaction(run) : await run();
|
|
124
141
|
return successOperation(data);
|
|
125
142
|
},
|
|
126
143
|
inviteMember: async (input, request) => {
|
|
@@ -5,4 +5,4 @@ export type PasskeyPluginConfig<U extends UserBase, K extends keyof U> = {
|
|
|
5
5
|
requiredProfileFields: readonly K[];
|
|
6
6
|
passkeys: PasskeyAdapter;
|
|
7
7
|
};
|
|
8
|
-
export declare const passkeyPlugin: <U extends UserBase, K extends keyof U>(config: PasskeyPluginConfig<U, K>) => AuthMethodPlugin<"passkey", PasskeyRegisterInput<U, K>, PasskeyAuthenticateInput, U>;
|
|
8
|
+
export declare const passkeyPlugin: <U extends UserBase, K extends keyof U>(config: PasskeyPluginConfig<U, K>) => AuthMethodPlugin<"passkey", PasskeyRegisterInput<U, K>, PasskeyAuthenticateInput, U, never, true, false>;
|
package/dist/plugins/passkey.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { cloneWithout, createId } from "../core/utils.js";
|
|
2
|
+
import { ensureFields } from "../core/validators.js";
|
|
1
3
|
import { AuthError } from "../errors/index.js";
|
|
2
4
|
import { createIssue } from "../issues/index.js";
|
|
3
5
|
import { errorOperation, successOperation } from "../types/results.js";
|
|
4
|
-
import { cloneWithout, createId } from "../core/utils.js";
|
|
5
|
-
import { ensureFields } from "../core/validators.js";
|
|
6
6
|
export const passkeyPlugin = (config) => ({
|
|
7
7
|
kind: "auth_method",
|
|
8
8
|
method: "passkey",
|
|
@@ -54,10 +54,7 @@ export const passkeyPlugin = (config) => ({
|
|
|
54
54
|
}
|
|
55
55
|
let user = await ctx.adapters.users.findByEmail(input.email);
|
|
56
56
|
if (!user) {
|
|
57
|
-
const payload = cloneWithout(input, [
|
|
58
|
-
"method",
|
|
59
|
-
"registration",
|
|
60
|
-
]);
|
|
57
|
+
const payload = cloneWithout(input, ["method", "registration"]);
|
|
61
58
|
payload.emailVerified = true;
|
|
62
59
|
user = await ctx.adapters.users.create(payload);
|
|
63
60
|
}
|
|
@@ -7,4 +7,4 @@ export type PasswordPluginConfig<U extends UserBase, K extends keyof U> = {
|
|
|
7
7
|
hashPassword?: (password: string) => string;
|
|
8
8
|
verifyPassword?: (password: string, hash: string) => boolean;
|
|
9
9
|
};
|
|
10
|
-
export declare const passwordPlugin: <U extends UserBase, K extends keyof U>(config: PasswordPluginConfig<U, K>) => AuthMethodPlugin<"password", PasswordRegisterInput<U, K>, PasswordAuthenticateInput, U>;
|
|
10
|
+
export declare const passwordPlugin: <U extends UserBase, K extends keyof U>(config: PasswordPluginConfig<U, K>) => AuthMethodPlugin<"password", PasswordRegisterInput<U, K>, PasswordAuthenticateInput, U, never, true, false>;
|
package/dist/plugins/password.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { cloneWithout, secretHash, secretVerify } from "../core/utils.js";
|
|
2
|
+
import { ensureFields } from "../core/validators.js";
|
|
1
3
|
import { AuthError } from "../errors/index.js";
|
|
2
4
|
import { createIssue } from "../issues/index.js";
|
|
3
5
|
import { errorOperation, successOperation } from "../types/results.js";
|
|
4
|
-
import { cloneWithout, secretHash, secretVerify } from "../core/utils.js";
|
|
5
|
-
import { ensureFields } from "../core/validators.js";
|
|
6
6
|
export const passwordPlugin = (config) => ({
|
|
7
7
|
kind: "auth_method",
|
|
8
8
|
method: "password",
|
|
@@ -25,10 +25,7 @@ export const passwordPlugin = (config) => ({
|
|
|
25
25
|
if (requiredError) {
|
|
26
26
|
return errorOperation(requiredError);
|
|
27
27
|
}
|
|
28
|
-
const payload = cloneWithout(input, [
|
|
29
|
-
"method",
|
|
30
|
-
"password",
|
|
31
|
-
]);
|
|
28
|
+
const payload = cloneWithout(input, ["method", "password"]);
|
|
32
29
|
if (payload.emailVerified === undefined) {
|
|
33
30
|
payload.emailVerified = false;
|
|
34
31
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { StripeCustomerAdapter, StripeSubscriptionAdapter, StripeTrialUsageAdapter, StripeWebhookEventAdapter } from "../types/adapters.js";
|
|
3
|
+
import type { StripePlan, StripeSubject, UserBase } from "../types/model.js";
|
|
4
|
+
import type { DomainPlugin, StripePlansResolver, StripePluginApi } from "../types/plugins.js";
|
|
5
|
+
type StripePluginHandlers<Feature extends string, LimitKey extends string> = {
|
|
6
|
+
customers: StripeCustomerAdapter;
|
|
7
|
+
subscriptions: StripeSubscriptionAdapter<Feature, LimitKey>;
|
|
8
|
+
events: StripeWebhookEventAdapter;
|
|
9
|
+
trials?: StripeTrialUsageAdapter;
|
|
10
|
+
};
|
|
11
|
+
export type StripePluginConfig<U extends UserBase, Feature extends string, LimitKey extends string> = {
|
|
12
|
+
stripe: Stripe;
|
|
13
|
+
webhookSecret: string;
|
|
14
|
+
plans: StripePlansResolver<Feature, LimitKey>;
|
|
15
|
+
handlers: StripePluginHandlers<Feature, LimitKey>;
|
|
16
|
+
customerMode?: "user" | "organization" | "both";
|
|
17
|
+
};
|
|
18
|
+
type StripePlanCatalogEntry<Feature extends string, LimitKey extends string> = Omit<StripePlan<Feature, LimitKey>, "scope"> & {
|
|
19
|
+
scope: StripeSubject["kind"];
|
|
20
|
+
};
|
|
21
|
+
type StripePlanCatalog<Feature extends string, LimitKey extends string> = readonly StripePlanCatalogEntry<Feature, LimitKey>[];
|
|
22
|
+
export declare const stripePlugin: <U extends UserBase, Feature extends string, LimitKey extends string, const Plans extends StripePlanCatalog<Feature, LimitKey>>(config: Omit<StripePluginConfig<U, Feature, LimitKey>, "plans"> & {
|
|
23
|
+
plans: StripePlansResolver<Feature, LimitKey, Plans>;
|
|
24
|
+
}) => DomainPlugin<"stripe", U, StripePluginApi<Feature, LimitKey>, true>;
|
|
25
|
+
export {};
|