@auth-gate/core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,6 @@ import { createAuthGateClient } from "@auth-gate/core";
15
15
 
16
16
  const client = createAuthGateClient({
17
17
  apiKey: process.env.AUTHGATE_API_KEY!,
18
- projectId: process.env.AUTHGATE_PROJECT_ID!,
19
18
  baseUrl: "https://authgate.dev", // or your self-hosted instance
20
19
  });
21
20
  ```
@@ -27,8 +26,8 @@ const client = createAuthGateClient({
27
26
  Supports Google, GitHub, Discord, Azure (Microsoft), and Apple.
28
27
 
29
28
  ```ts
30
- // Generate the authorization URL
31
- const url = client.getOAuthUrl("google", "https://yourapp.com/auth/callback");
29
+ // Generate the authorization URL (async — resolves project ID from API key)
30
+ const url = await client.getOAuthUrl("google", "https://yourapp.com/auth/callback");
32
31
 
33
32
  // After callback, verify the JWT token from AuthGate
34
33
  const result = await client.verifyToken(token);
@@ -144,8 +143,7 @@ client.getNonceCookieOptions();
144
143
 
145
144
  | Option | Type | Required | Description |
146
145
  |--------|------|----------|-------------|
147
- | `apiKey` | `string` | Yes | Your AuthGate API key |
148
- | `projectId` | `string` | Yes | Your AuthGate project ID |
146
+ | `apiKey` | `string` | Yes | Your AuthGate API key (also used to resolve project ID) |
149
147
  | `baseUrl` | `string` | Yes | AuthGate instance URL |
150
148
  | `cookieName` | `string` | No | Session cookie name (default: `__authgate`) |
151
149
  | `sessionMaxAge` | `number` | No | Session TTL in seconds (default: `604800` / 7 days) |
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ var index_exports = {};
39
39
  __export(index_exports, {
40
40
  AuthGateClient: () => AuthGateClient,
41
41
  AuthGateError: () => AuthGateError,
42
+ ConfigurationError: () => ConfigurationError,
42
43
  DEFAULT_COOKIE_NAME: () => DEFAULT_COOKIE_NAME,
43
44
  InvalidStateError: () => InvalidStateError,
44
45
  NONCE_COOKIE_NAME: () => NONCE_COOKIE_NAME,
@@ -48,7 +49,9 @@ __export(index_exports, {
48
49
  SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
49
50
  SessionDecryptionError: () => SessionDecryptionError,
50
51
  TokenVerificationError: () => TokenVerificationError,
51
- createAuthGateClient: () => createAuthGateClient
52
+ createAuthGateClient: () => createAuthGateClient,
53
+ hasFeature: () => hasFeature,
54
+ hasPlan: () => hasPlan
52
55
  });
53
56
  module.exports = __toCommonJS(index_exports);
54
57
 
@@ -296,6 +299,12 @@ var InvalidStateError = class extends AuthGateError {
296
299
  this.name = "InvalidStateError";
297
300
  }
298
301
  };
302
+ var ConfigurationError = class extends AuthGateError {
303
+ constructor(message) {
304
+ super(message);
305
+ this.name = "ConfigurationError";
306
+ }
307
+ };
299
308
 
300
309
  // src/client.ts
301
310
  var AuthGateClient = class {
@@ -306,6 +315,28 @@ var AuthGateClient = class {
306
315
  this.encryptionKey = null;
307
316
  this.stateKey = null;
308
317
  var _a, _b, _c;
318
+ if (!config.apiKey || typeof config.apiKey !== "string" || config.apiKey.trim() === "") {
319
+ throw new ConfigurationError(
320
+ "[AuthGate] Missing or empty `apiKey`. Get your API key from the AuthGate dashboard \u2192 Project \u2192 API Key."
321
+ );
322
+ }
323
+ if (!config.baseUrl || typeof config.baseUrl !== "string" || config.baseUrl.trim() === "") {
324
+ throw new ConfigurationError(
325
+ '[AuthGate] Missing or empty `baseUrl`. Set this to your AuthGate service URL (e.g. "https://authgate.dev").'
326
+ );
327
+ }
328
+ try {
329
+ new URL(config.baseUrl);
330
+ } catch (e) {
331
+ throw new ConfigurationError(
332
+ `[AuthGate] Invalid \`baseUrl\`: "${config.baseUrl}". Must be a valid URL (e.g. "https://authgate.dev").`
333
+ );
334
+ }
335
+ if (config.callbackPath !== void 0 && !config.callbackPath.startsWith("/")) {
336
+ throw new ConfigurationError(
337
+ `[AuthGate] Invalid \`callbackPath\`: "${config.callbackPath}". Must start with "/" (e.g. "/api/auth/callback").`
338
+ );
339
+ }
309
340
  this.config = __spreadProps(__spreadValues({}, config), {
310
341
  cookieName: (_a = config.cookieName) != null ? _a : DEFAULT_COOKIE_NAME,
311
342
  sessionMaxAge: (_b = config.sessionMaxAge) != null ? _b : SESSION_MAX_AGE,
@@ -453,17 +484,28 @@ var AuthGateClient = class {
453
484
  * @returns The full authorization URL to redirect the user to.
454
485
  * @throws {@link AuthGateError} if the provider is not in {@link SUPPORTED_PROVIDERS}.
455
486
  */
456
- getOAuthUrl(provider, callbackUrl, options) {
487
+ async getOAuthUrl(provider, callbackUrl, options) {
457
488
  if (!SUPPORTED_PROVIDERS.includes(provider)) {
458
489
  throw new AuthGateError(`Unsupported provider: ${provider}`);
459
490
  }
460
- const url = new URL(`/api/proxy/${provider}`, this.config.baseUrl);
461
- url.searchParams.set("project_id", this.config.projectId);
462
- url.searchParams.set("callback_url", callbackUrl);
463
- if (options == null ? void 0 : options.linkTo) {
464
- url.searchParams.set("link_to", options.linkTo);
491
+ const response = await fetch(`${this.config.baseUrl}/api/proxy/${provider}`, {
492
+ method: "POST",
493
+ headers: {
494
+ Authorization: `Bearer ${this.config.apiKey}`,
495
+ "Content-Type": "application/json"
496
+ },
497
+ body: JSON.stringify({
498
+ callback_url: callbackUrl,
499
+ state: options == null ? void 0 : options.state,
500
+ link_to: options == null ? void 0 : options.linkTo
501
+ })
502
+ });
503
+ if (!response.ok) {
504
+ const error = await response.json().catch(() => ({}));
505
+ throw new AuthGateError(error.error || `OAuth initiation failed: ${response.status}`);
465
506
  }
466
- return url.toString();
507
+ const data = await response.json();
508
+ return data.redirect_url;
467
509
  }
468
510
  // ---------------------------------------------------------------------------
469
511
  // Email auth
@@ -477,12 +519,14 @@ var AuthGateClient = class {
477
519
  async emailSignup(data) {
478
520
  return fetch(`${this.config.baseUrl}/api/proxy/email/signup`, {
479
521
  method: "POST",
480
- headers: { "Content-Type": "application/json" },
522
+ headers: {
523
+ Authorization: `Bearer ${this.config.apiKey}`,
524
+ "Content-Type": "application/json"
525
+ },
481
526
  body: JSON.stringify({
482
527
  email: data.email,
483
528
  password: data.password,
484
529
  name: data.name,
485
- project_id: this.config.projectId,
486
530
  callback_url: data.callbackUrl
487
531
  })
488
532
  });
@@ -499,11 +543,13 @@ var AuthGateClient = class {
499
543
  async emailSignin(data) {
500
544
  return fetch(`${this.config.baseUrl}/api/proxy/email/signin`, {
501
545
  method: "POST",
502
- headers: { "Content-Type": "application/json" },
546
+ headers: {
547
+ Authorization: `Bearer ${this.config.apiKey}`,
548
+ "Content-Type": "application/json"
549
+ },
503
550
  body: JSON.stringify({
504
551
  email: data.email,
505
552
  password: data.password,
506
- project_id: this.config.projectId,
507
553
  callback_url: data.callbackUrl
508
554
  })
509
555
  });
@@ -517,10 +563,12 @@ var AuthGateClient = class {
517
563
  async emailForgotPassword(data) {
518
564
  return fetch(`${this.config.baseUrl}/api/proxy/email/reset-password`, {
519
565
  method: "POST",
520
- headers: { "Content-Type": "application/json" },
566
+ headers: {
567
+ Authorization: `Bearer ${this.config.apiKey}`,
568
+ "Content-Type": "application/json"
569
+ },
521
570
  body: JSON.stringify({
522
571
  email: data.email,
523
- project_id: this.config.projectId,
524
572
  callback_url: data.callbackUrl
525
573
  })
526
574
  });
@@ -536,11 +584,13 @@ var AuthGateClient = class {
536
584
  `${this.config.baseUrl}/api/proxy/email/reset-password/confirm`,
537
585
  {
538
586
  method: "POST",
539
- headers: { "Content-Type": "application/json" },
587
+ headers: {
588
+ Authorization: `Bearer ${this.config.apiKey}`,
589
+ "Content-Type": "application/json"
590
+ },
540
591
  body: JSON.stringify({
541
592
  token: data.token,
542
- password: data.password,
543
- project_id: this.config.projectId
593
+ password: data.password
544
594
  })
545
595
  }
546
596
  );
@@ -559,10 +609,12 @@ var AuthGateClient = class {
559
609
  `${this.config.baseUrl}/api/proxy/email/magic-link/send`,
560
610
  {
561
611
  method: "POST",
562
- headers: { "Content-Type": "application/json" },
612
+ headers: {
613
+ Authorization: `Bearer ${this.config.apiKey}`,
614
+ "Content-Type": "application/json"
615
+ },
563
616
  body: JSON.stringify({
564
617
  email: data.email,
565
- project_id: this.config.projectId,
566
618
  callback_url: data.callbackUrl
567
619
  })
568
620
  }
@@ -580,11 +632,13 @@ var AuthGateClient = class {
580
632
  async emailVerifyCode(data) {
581
633
  return fetch(`${this.config.baseUrl}/api/proxy/email/verify`, {
582
634
  method: "POST",
583
- headers: { "Content-Type": "application/json" },
635
+ headers: {
636
+ Authorization: `Bearer ${this.config.apiKey}`,
637
+ "Content-Type": "application/json"
638
+ },
584
639
  body: JSON.stringify({
585
640
  email: data.email,
586
- code: data.code,
587
- project_id: this.config.projectId
641
+ code: data.code
588
642
  })
589
643
  });
590
644
  }
@@ -600,10 +654,12 @@ var AuthGateClient = class {
600
654
  async smsSendCode(data) {
601
655
  return fetch(`${this.config.baseUrl}/api/proxy/sms/send-code`, {
602
656
  method: "POST",
603
- headers: { "Content-Type": "application/json" },
657
+ headers: {
658
+ Authorization: `Bearer ${this.config.apiKey}`,
659
+ "Content-Type": "application/json"
660
+ },
604
661
  body: JSON.stringify({
605
662
  phone: data.phone,
606
- project_id: this.config.projectId,
607
663
  callback_url: data.callbackUrl
608
664
  })
609
665
  });
@@ -617,11 +673,13 @@ var AuthGateClient = class {
617
673
  async smsVerifyCode(data) {
618
674
  return fetch(`${this.config.baseUrl}/api/proxy/sms/verify-code`, {
619
675
  method: "POST",
620
- headers: { "Content-Type": "application/json" },
676
+ headers: {
677
+ Authorization: `Bearer ${this.config.apiKey}`,
678
+ "Content-Type": "application/json"
679
+ },
621
680
  body: JSON.stringify({
622
681
  phone: data.phone,
623
682
  code: data.code,
624
- project_id: this.config.projectId,
625
683
  callback_url: data.callbackUrl
626
684
  })
627
685
  });
@@ -644,10 +702,12 @@ var AuthGateClient = class {
644
702
  `${this.config.baseUrl}/api/proxy/token/refresh`,
645
703
  {
646
704
  method: "POST",
647
- headers: { "Content-Type": "application/json" },
705
+ headers: {
706
+ Authorization: `Bearer ${this.config.apiKey}`,
707
+ "Content-Type": "application/json"
708
+ },
648
709
  body: JSON.stringify({
649
- refresh_token: refreshToken,
650
- project_id: this.config.projectId
710
+ refresh_token: refreshToken
651
711
  })
652
712
  }
653
713
  );
@@ -672,10 +732,12 @@ var AuthGateClient = class {
672
732
  `${this.config.baseUrl}/api/proxy/sessions/revoke`,
673
733
  {
674
734
  method: "POST",
675
- headers: { "Content-Type": "application/json" },
735
+ headers: {
736
+ Authorization: `Bearer ${this.config.apiKey}`,
737
+ "Content-Type": "application/json"
738
+ },
676
739
  body: JSON.stringify({
677
- refresh_token: refreshToken,
678
- project_id: this.config.projectId
740
+ refresh_token: refreshToken
679
741
  })
680
742
  }
681
743
  );
@@ -698,10 +760,11 @@ var AuthGateClient = class {
698
760
  async mfaVerify(data) {
699
761
  return fetch(`${this.config.baseUrl}/api/proxy/mfa/verify`, {
700
762
  method: "POST",
701
- headers: { "Content-Type": "application/json" },
702
- body: JSON.stringify(__spreadProps(__spreadValues({}, data), {
703
- project_id: this.config.projectId
704
- }))
763
+ headers: {
764
+ Authorization: `Bearer ${this.config.apiKey}`,
765
+ "Content-Type": "application/json"
766
+ },
767
+ body: JSON.stringify(data)
705
768
  });
706
769
  }
707
770
  /**
@@ -832,11 +895,11 @@ var AuthGateClient = class {
832
895
  async mfaSmsSendCode(challenge) {
833
896
  return fetch(`${this.config.baseUrl}/api/proxy/mfa/sms/send-code`, {
834
897
  method: "POST",
835
- headers: { "Content-Type": "application/json" },
836
- body: JSON.stringify({
837
- challenge,
838
- project_id: this.config.projectId
839
- })
898
+ headers: {
899
+ Authorization: `Bearer ${this.config.apiKey}`,
900
+ "Content-Type": "application/json"
901
+ },
902
+ body: JSON.stringify({ challenge })
840
903
  });
841
904
  }
842
905
  // ---------------------------------------------------------------------------
@@ -858,10 +921,6 @@ var AuthGateClient = class {
858
921
  get callbackPath() {
859
922
  return this.config.callbackPath;
860
923
  }
861
- /** The AuthGate project ID. */
862
- get projectId() {
863
- return this.config.projectId;
864
- }
865
924
  /** The AuthGate service base URL. */
866
925
  get baseUrl() {
867
926
  return this.config.baseUrl;
@@ -899,7 +958,71 @@ var AuthGateClient = class {
899
958
  path: "/"
900
959
  };
901
960
  }
961
+ // ---------------------------------------------------------------------------
962
+ // Config validation (async, called by framework factories)
963
+ // ---------------------------------------------------------------------------
964
+ /**
965
+ * Validate the SDK configuration against the AuthGate server.
966
+ *
967
+ * Checks that the API key is valid and (optionally) that a callback URL
968
+ * is registered for the project. This is called automatically by framework
969
+ * factories (`createAuthGate`, `createAuthRoutes`) and logs errors to the
970
+ * console — it does NOT throw.
971
+ *
972
+ * @param callbackUrl - The full callback URL to check registration for.
973
+ * @internal
974
+ */
975
+ async validateConfig(callbackUrl) {
976
+ try {
977
+ const response = await fetch(
978
+ `${this.config.baseUrl}/api/v1/config/validate`,
979
+ {
980
+ method: "POST",
981
+ headers: {
982
+ Authorization: `Bearer ${this.config.apiKey}`,
983
+ "Content-Type": "application/json"
984
+ },
985
+ body: JSON.stringify({
986
+ callback_url: callbackUrl
987
+ })
988
+ }
989
+ );
990
+ if (response.status === 401) {
991
+ console.error(
992
+ "\n[AuthGate] \u2717 Invalid API key.\n The API key you provided is not recognized or has been revoked.\n \u2192 Go to your AuthGate dashboard \u2192 Project \u2192 API Key to get a valid key.\n"
993
+ );
994
+ return;
995
+ }
996
+ if (!response.ok) {
997
+ console.warn(
998
+ "[AuthGate] Could not validate config (server returned " + response.status + "). Auth flows may fail."
999
+ );
1000
+ return;
1001
+ }
1002
+ const data = await response.json();
1003
+ if (callbackUrl && data.callback_url_registered === false) {
1004
+ console.error(
1005
+ "\n[AuthGate] \u2717 Callback URL not registered: " + callbackUrl + "\n This URL must be added to your project's allowed callback URLs.\n \u2192 Go to your AuthGate dashboard \u2192 Project \u2192 Callback URLs \u2192 Add URL.\n"
1006
+ );
1007
+ }
1008
+ } catch (e) {
1009
+ console.warn(
1010
+ "[AuthGate] Could not reach AuthGate server at " + this.config.baseUrl + " to validate config. Auth flows may fail if the URL is wrong."
1011
+ );
1012
+ }
1013
+ }
902
1014
  };
903
1015
  function createAuthGateClient(config) {
904
1016
  return new AuthGateClient(config);
905
1017
  }
1018
+
1019
+ // src/billing.ts
1020
+ function hasPlan(entitlements, plan) {
1021
+ if (!(entitlements == null ? void 0 : entitlements.plan)) return false;
1022
+ return entitlements.plan.toLowerCase() === plan.toLowerCase();
1023
+ }
1024
+ function hasFeature(entitlements, feature) {
1025
+ if (!(entitlements == null ? void 0 : entitlements.features)) return false;
1026
+ const lower = feature.toLowerCase();
1027
+ return entitlements.features.some((f) => f.toLowerCase() === lower);
1028
+ }
package/dist/index.d.cts CHANGED
@@ -47,16 +47,13 @@ interface TokenVerifyResult {
47
47
  *
48
48
  * const client = createAuthGateClient({
49
49
  * apiKey: process.env.AUTHGATE_API_KEY!,
50
- * projectId: process.env.AUTHGATE_PROJECT_ID!,
51
50
  * baseUrl: "https://authgate.dev",
52
51
  * });
53
52
  * ```
54
53
  */
55
54
  interface AuthGateConfig {
56
- /** Server-side API key for your AuthGate project. Found in the dashboard under API Keys. */
55
+ /** Server-side API key for your AuthGate project. Found in the dashboard under API Key. */
57
56
  apiKey: string;
58
- /** The project ID from your AuthGate dashboard. */
59
- projectId: string;
60
57
  /** Base URL of the AuthGate service (e.g. `"https://authgate.dev"`). */
61
58
  baseUrl: string;
62
59
  /**
@@ -172,7 +169,6 @@ interface BackupCodesResponse {
172
169
  * ```ts
173
170
  * const client = new AuthGateClient({
174
171
  * apiKey: process.env.AUTHGATE_API_KEY!,
175
- * projectId: process.env.AUTHGATE_PROJECT_ID!,
176
172
  * baseUrl: "https://authgate.dev",
177
173
  * });
178
174
  * ```
@@ -262,8 +258,9 @@ declare class AuthGateClient {
262
258
  * @throws {@link AuthGateError} if the provider is not in {@link SUPPORTED_PROVIDERS}.
263
259
  */
264
260
  getOAuthUrl(provider: OAuthProvider, callbackUrl: string, options?: {
261
+ state?: string;
265
262
  linkTo?: string;
266
- }): string;
263
+ }): Promise<string>;
267
264
  /**
268
265
  * Register a new user with email and password.
269
266
  *
@@ -459,8 +456,6 @@ declare class AuthGateClient {
459
456
  get refreshTokenCookieName(): string;
460
457
  /** The callback path where OAuth redirects are handled (default: `"/api/auth/callback"`). */
461
458
  get callbackPath(): string;
462
- /** The AuthGate project ID. */
463
- get projectId(): string;
464
459
  /** The AuthGate service base URL. */
465
460
  get baseUrl(): string;
466
461
  /** Session lifetime in seconds. */
@@ -477,6 +472,18 @@ declare class AuthGateClient {
477
472
  * Short-lived (10 minutes) to match the state TTL.
478
473
  */
479
474
  getNonceCookieOptions(): CookieOptions;
475
+ /**
476
+ * Validate the SDK configuration against the AuthGate server.
477
+ *
478
+ * Checks that the API key is valid and (optionally) that a callback URL
479
+ * is registered for the project. This is called automatically by framework
480
+ * factories (`createAuthGate`, `createAuthRoutes`) and logs errors to the
481
+ * console — it does NOT throw.
482
+ *
483
+ * @param callbackUrl - The full callback URL to check registration for.
484
+ * @internal
485
+ */
486
+ validateConfig(callbackUrl?: string): Promise<void>;
480
487
  }
481
488
  /**
482
489
  * Create an {@link AuthGateClient} instance.
@@ -557,5 +564,79 @@ declare class SessionDecryptionError extends AuthGateError {
557
564
  declare class InvalidStateError extends AuthGateError {
558
565
  constructor(message?: string);
559
566
  }
567
+ /**
568
+ * Thrown when the AuthGate SDK is initialized with invalid configuration.
569
+ *
570
+ * Check the error message for details on which config field is invalid
571
+ * and how to fix it.
572
+ */
573
+ declare class ConfigurationError extends AuthGateError {
574
+ constructor(message: string);
575
+ }
576
+
577
+ /** A product/plan in the billing catalog. */
578
+ interface BillingPlan {
579
+ id: string;
580
+ name: string;
581
+ description: string | null;
582
+ features: string[];
583
+ prices: BillingPrice[];
584
+ }
585
+ /** A price attached to a plan. */
586
+ interface BillingPrice {
587
+ id: string;
588
+ type: "recurring" | "metered" | "tiered";
589
+ amount: number | null;
590
+ currency: string;
591
+ interval: "daily" | "weekly" | "monthly" | "yearly";
592
+ intervalCount: number;
593
+ }
594
+ /** An active subscription for an end user. */
595
+ interface BillingSubscription {
596
+ id: string;
597
+ status: "active" | "past_due" | "canceled" | "paused" | "trialing" | "unpaid";
598
+ plan: {
599
+ id: string;
600
+ name: string;
601
+ } | null;
602
+ price: BillingPrice | null;
603
+ currentPeriodStart: string;
604
+ currentPeriodEnd: string;
605
+ cancelAtPeriodEnd: boolean;
606
+ trialEnd: string | null;
607
+ }
608
+ /** Entitlement data for an end user — what plan they're on and what features they have. */
609
+ interface BillingEntitlement {
610
+ plan: string | null;
611
+ features: string[];
612
+ status: string | null;
613
+ subscription: {
614
+ id: string;
615
+ currentPeriodEnd: string;
616
+ } | null;
617
+ }
618
+ /** A single usage event to report. */
619
+ interface UsageEvent {
620
+ metric: string;
621
+ quantity: number;
622
+ idempotencyKey?: string;
623
+ timestamp?: string;
624
+ }
625
+ /**
626
+ * Check whether the user's entitlements include a specific plan.
627
+ *
628
+ * @param entitlements - The entitlement object from the billing API.
629
+ * @param plan - The plan name to check (case-insensitive).
630
+ * @returns `true` if the user is on the given plan.
631
+ */
632
+ declare function hasPlan(entitlements: BillingEntitlement | null | undefined, plan: string): boolean;
633
+ /**
634
+ * Check whether the user's entitlements include a specific feature.
635
+ *
636
+ * @param entitlements - The entitlement object from the billing API.
637
+ * @param feature - The feature name to check (case-insensitive).
638
+ * @returns `true` if the user has the given feature.
639
+ */
640
+ declare function hasFeature(entitlements: BillingEntitlement | null | undefined, feature: string): boolean;
560
641
 
561
- export { AuthGateClient, type AuthGateConfig, AuthGateError, type AuthGateUser, type BackupCodesResponse, type CookieOptions, DEFAULT_COOKIE_NAME, InvalidStateError, type MfaChallengeResponse, type MfaStatus, NONCE_COOKIE_NAME, type OAuthProvider, REFRESH_TOKEN_COOKIE_NAME, SESSION_MAX_AGE, STATE_TTL_MS, SUPPORTED_PROVIDERS, SessionDecryptionError, type SessionPayload, TokenVerificationError, type TokenVerifyResult, type TotpSetupResponse, createAuthGateClient };
642
+ export { AuthGateClient, type AuthGateConfig, AuthGateError, type AuthGateUser, type BackupCodesResponse, type BillingEntitlement, type BillingPlan, type BillingPrice, type BillingSubscription, ConfigurationError, type CookieOptions, DEFAULT_COOKIE_NAME, InvalidStateError, type MfaChallengeResponse, type MfaStatus, NONCE_COOKIE_NAME, type OAuthProvider, REFRESH_TOKEN_COOKIE_NAME, SESSION_MAX_AGE, STATE_TTL_MS, SUPPORTED_PROVIDERS, SessionDecryptionError, type SessionPayload, TokenVerificationError, type TokenVerifyResult, type TotpSetupResponse, type UsageEvent, createAuthGateClient, hasFeature, hasPlan };
package/dist/index.d.ts CHANGED
@@ -47,16 +47,13 @@ interface TokenVerifyResult {
47
47
  *
48
48
  * const client = createAuthGateClient({
49
49
  * apiKey: process.env.AUTHGATE_API_KEY!,
50
- * projectId: process.env.AUTHGATE_PROJECT_ID!,
51
50
  * baseUrl: "https://authgate.dev",
52
51
  * });
53
52
  * ```
54
53
  */
55
54
  interface AuthGateConfig {
56
- /** Server-side API key for your AuthGate project. Found in the dashboard under API Keys. */
55
+ /** Server-side API key for your AuthGate project. Found in the dashboard under API Key. */
57
56
  apiKey: string;
58
- /** The project ID from your AuthGate dashboard. */
59
- projectId: string;
60
57
  /** Base URL of the AuthGate service (e.g. `"https://authgate.dev"`). */
61
58
  baseUrl: string;
62
59
  /**
@@ -172,7 +169,6 @@ interface BackupCodesResponse {
172
169
  * ```ts
173
170
  * const client = new AuthGateClient({
174
171
  * apiKey: process.env.AUTHGATE_API_KEY!,
175
- * projectId: process.env.AUTHGATE_PROJECT_ID!,
176
172
  * baseUrl: "https://authgate.dev",
177
173
  * });
178
174
  * ```
@@ -262,8 +258,9 @@ declare class AuthGateClient {
262
258
  * @throws {@link AuthGateError} if the provider is not in {@link SUPPORTED_PROVIDERS}.
263
259
  */
264
260
  getOAuthUrl(provider: OAuthProvider, callbackUrl: string, options?: {
261
+ state?: string;
265
262
  linkTo?: string;
266
- }): string;
263
+ }): Promise<string>;
267
264
  /**
268
265
  * Register a new user with email and password.
269
266
  *
@@ -459,8 +456,6 @@ declare class AuthGateClient {
459
456
  get refreshTokenCookieName(): string;
460
457
  /** The callback path where OAuth redirects are handled (default: `"/api/auth/callback"`). */
461
458
  get callbackPath(): string;
462
- /** The AuthGate project ID. */
463
- get projectId(): string;
464
459
  /** The AuthGate service base URL. */
465
460
  get baseUrl(): string;
466
461
  /** Session lifetime in seconds. */
@@ -477,6 +472,18 @@ declare class AuthGateClient {
477
472
  * Short-lived (10 minutes) to match the state TTL.
478
473
  */
479
474
  getNonceCookieOptions(): CookieOptions;
475
+ /**
476
+ * Validate the SDK configuration against the AuthGate server.
477
+ *
478
+ * Checks that the API key is valid and (optionally) that a callback URL
479
+ * is registered for the project. This is called automatically by framework
480
+ * factories (`createAuthGate`, `createAuthRoutes`) and logs errors to the
481
+ * console — it does NOT throw.
482
+ *
483
+ * @param callbackUrl - The full callback URL to check registration for.
484
+ * @internal
485
+ */
486
+ validateConfig(callbackUrl?: string): Promise<void>;
480
487
  }
481
488
  /**
482
489
  * Create an {@link AuthGateClient} instance.
@@ -557,5 +564,79 @@ declare class SessionDecryptionError extends AuthGateError {
557
564
  declare class InvalidStateError extends AuthGateError {
558
565
  constructor(message?: string);
559
566
  }
567
+ /**
568
+ * Thrown when the AuthGate SDK is initialized with invalid configuration.
569
+ *
570
+ * Check the error message for details on which config field is invalid
571
+ * and how to fix it.
572
+ */
573
+ declare class ConfigurationError extends AuthGateError {
574
+ constructor(message: string);
575
+ }
576
+
577
+ /** A product/plan in the billing catalog. */
578
+ interface BillingPlan {
579
+ id: string;
580
+ name: string;
581
+ description: string | null;
582
+ features: string[];
583
+ prices: BillingPrice[];
584
+ }
585
+ /** A price attached to a plan. */
586
+ interface BillingPrice {
587
+ id: string;
588
+ type: "recurring" | "metered" | "tiered";
589
+ amount: number | null;
590
+ currency: string;
591
+ interval: "daily" | "weekly" | "monthly" | "yearly";
592
+ intervalCount: number;
593
+ }
594
+ /** An active subscription for an end user. */
595
+ interface BillingSubscription {
596
+ id: string;
597
+ status: "active" | "past_due" | "canceled" | "paused" | "trialing" | "unpaid";
598
+ plan: {
599
+ id: string;
600
+ name: string;
601
+ } | null;
602
+ price: BillingPrice | null;
603
+ currentPeriodStart: string;
604
+ currentPeriodEnd: string;
605
+ cancelAtPeriodEnd: boolean;
606
+ trialEnd: string | null;
607
+ }
608
+ /** Entitlement data for an end user — what plan they're on and what features they have. */
609
+ interface BillingEntitlement {
610
+ plan: string | null;
611
+ features: string[];
612
+ status: string | null;
613
+ subscription: {
614
+ id: string;
615
+ currentPeriodEnd: string;
616
+ } | null;
617
+ }
618
+ /** A single usage event to report. */
619
+ interface UsageEvent {
620
+ metric: string;
621
+ quantity: number;
622
+ idempotencyKey?: string;
623
+ timestamp?: string;
624
+ }
625
+ /**
626
+ * Check whether the user's entitlements include a specific plan.
627
+ *
628
+ * @param entitlements - The entitlement object from the billing API.
629
+ * @param plan - The plan name to check (case-insensitive).
630
+ * @returns `true` if the user is on the given plan.
631
+ */
632
+ declare function hasPlan(entitlements: BillingEntitlement | null | undefined, plan: string): boolean;
633
+ /**
634
+ * Check whether the user's entitlements include a specific feature.
635
+ *
636
+ * @param entitlements - The entitlement object from the billing API.
637
+ * @param feature - The feature name to check (case-insensitive).
638
+ * @returns `true` if the user has the given feature.
639
+ */
640
+ declare function hasFeature(entitlements: BillingEntitlement | null | undefined, feature: string): boolean;
560
641
 
561
- export { AuthGateClient, type AuthGateConfig, AuthGateError, type AuthGateUser, type BackupCodesResponse, type CookieOptions, DEFAULT_COOKIE_NAME, InvalidStateError, type MfaChallengeResponse, type MfaStatus, NONCE_COOKIE_NAME, type OAuthProvider, REFRESH_TOKEN_COOKIE_NAME, SESSION_MAX_AGE, STATE_TTL_MS, SUPPORTED_PROVIDERS, SessionDecryptionError, type SessionPayload, TokenVerificationError, type TokenVerifyResult, type TotpSetupResponse, createAuthGateClient };
642
+ export { AuthGateClient, type AuthGateConfig, AuthGateError, type AuthGateUser, type BackupCodesResponse, type BillingEntitlement, type BillingPlan, type BillingPrice, type BillingSubscription, ConfigurationError, type CookieOptions, DEFAULT_COOKIE_NAME, InvalidStateError, type MfaChallengeResponse, type MfaStatus, NONCE_COOKIE_NAME, type OAuthProvider, REFRESH_TOKEN_COOKIE_NAME, SESSION_MAX_AGE, STATE_TTL_MS, SUPPORTED_PROVIDERS, SessionDecryptionError, type SessionPayload, TokenVerificationError, type TokenVerifyResult, type TotpSetupResponse, type UsageEvent, createAuthGateClient, hasFeature, hasPlan };
package/dist/index.mjs CHANGED
@@ -262,6 +262,12 @@ var InvalidStateError = class extends AuthGateError {
262
262
  this.name = "InvalidStateError";
263
263
  }
264
264
  };
265
+ var ConfigurationError = class extends AuthGateError {
266
+ constructor(message) {
267
+ super(message);
268
+ this.name = "ConfigurationError";
269
+ }
270
+ };
265
271
 
266
272
  // src/client.ts
267
273
  var AuthGateClient = class {
@@ -272,6 +278,28 @@ var AuthGateClient = class {
272
278
  this.encryptionKey = null;
273
279
  this.stateKey = null;
274
280
  var _a, _b, _c;
281
+ if (!config.apiKey || typeof config.apiKey !== "string" || config.apiKey.trim() === "") {
282
+ throw new ConfigurationError(
283
+ "[AuthGate] Missing or empty `apiKey`. Get your API key from the AuthGate dashboard \u2192 Project \u2192 API Key."
284
+ );
285
+ }
286
+ if (!config.baseUrl || typeof config.baseUrl !== "string" || config.baseUrl.trim() === "") {
287
+ throw new ConfigurationError(
288
+ '[AuthGate] Missing or empty `baseUrl`. Set this to your AuthGate service URL (e.g. "https://authgate.dev").'
289
+ );
290
+ }
291
+ try {
292
+ new URL(config.baseUrl);
293
+ } catch (e) {
294
+ throw new ConfigurationError(
295
+ `[AuthGate] Invalid \`baseUrl\`: "${config.baseUrl}". Must be a valid URL (e.g. "https://authgate.dev").`
296
+ );
297
+ }
298
+ if (config.callbackPath !== void 0 && !config.callbackPath.startsWith("/")) {
299
+ throw new ConfigurationError(
300
+ `[AuthGate] Invalid \`callbackPath\`: "${config.callbackPath}". Must start with "/" (e.g. "/api/auth/callback").`
301
+ );
302
+ }
275
303
  this.config = __spreadProps(__spreadValues({}, config), {
276
304
  cookieName: (_a = config.cookieName) != null ? _a : DEFAULT_COOKIE_NAME,
277
305
  sessionMaxAge: (_b = config.sessionMaxAge) != null ? _b : SESSION_MAX_AGE,
@@ -419,17 +447,28 @@ var AuthGateClient = class {
419
447
  * @returns The full authorization URL to redirect the user to.
420
448
  * @throws {@link AuthGateError} if the provider is not in {@link SUPPORTED_PROVIDERS}.
421
449
  */
422
- getOAuthUrl(provider, callbackUrl, options) {
450
+ async getOAuthUrl(provider, callbackUrl, options) {
423
451
  if (!SUPPORTED_PROVIDERS.includes(provider)) {
424
452
  throw new AuthGateError(`Unsupported provider: ${provider}`);
425
453
  }
426
- const url = new URL(`/api/proxy/${provider}`, this.config.baseUrl);
427
- url.searchParams.set("project_id", this.config.projectId);
428
- url.searchParams.set("callback_url", callbackUrl);
429
- if (options == null ? void 0 : options.linkTo) {
430
- url.searchParams.set("link_to", options.linkTo);
454
+ const response = await fetch(`${this.config.baseUrl}/api/proxy/${provider}`, {
455
+ method: "POST",
456
+ headers: {
457
+ Authorization: `Bearer ${this.config.apiKey}`,
458
+ "Content-Type": "application/json"
459
+ },
460
+ body: JSON.stringify({
461
+ callback_url: callbackUrl,
462
+ state: options == null ? void 0 : options.state,
463
+ link_to: options == null ? void 0 : options.linkTo
464
+ })
465
+ });
466
+ if (!response.ok) {
467
+ const error = await response.json().catch(() => ({}));
468
+ throw new AuthGateError(error.error || `OAuth initiation failed: ${response.status}`);
431
469
  }
432
- return url.toString();
470
+ const data = await response.json();
471
+ return data.redirect_url;
433
472
  }
434
473
  // ---------------------------------------------------------------------------
435
474
  // Email auth
@@ -443,12 +482,14 @@ var AuthGateClient = class {
443
482
  async emailSignup(data) {
444
483
  return fetch(`${this.config.baseUrl}/api/proxy/email/signup`, {
445
484
  method: "POST",
446
- headers: { "Content-Type": "application/json" },
485
+ headers: {
486
+ Authorization: `Bearer ${this.config.apiKey}`,
487
+ "Content-Type": "application/json"
488
+ },
447
489
  body: JSON.stringify({
448
490
  email: data.email,
449
491
  password: data.password,
450
492
  name: data.name,
451
- project_id: this.config.projectId,
452
493
  callback_url: data.callbackUrl
453
494
  })
454
495
  });
@@ -465,11 +506,13 @@ var AuthGateClient = class {
465
506
  async emailSignin(data) {
466
507
  return fetch(`${this.config.baseUrl}/api/proxy/email/signin`, {
467
508
  method: "POST",
468
- headers: { "Content-Type": "application/json" },
509
+ headers: {
510
+ Authorization: `Bearer ${this.config.apiKey}`,
511
+ "Content-Type": "application/json"
512
+ },
469
513
  body: JSON.stringify({
470
514
  email: data.email,
471
515
  password: data.password,
472
- project_id: this.config.projectId,
473
516
  callback_url: data.callbackUrl
474
517
  })
475
518
  });
@@ -483,10 +526,12 @@ var AuthGateClient = class {
483
526
  async emailForgotPassword(data) {
484
527
  return fetch(`${this.config.baseUrl}/api/proxy/email/reset-password`, {
485
528
  method: "POST",
486
- headers: { "Content-Type": "application/json" },
529
+ headers: {
530
+ Authorization: `Bearer ${this.config.apiKey}`,
531
+ "Content-Type": "application/json"
532
+ },
487
533
  body: JSON.stringify({
488
534
  email: data.email,
489
- project_id: this.config.projectId,
490
535
  callback_url: data.callbackUrl
491
536
  })
492
537
  });
@@ -502,11 +547,13 @@ var AuthGateClient = class {
502
547
  `${this.config.baseUrl}/api/proxy/email/reset-password/confirm`,
503
548
  {
504
549
  method: "POST",
505
- headers: { "Content-Type": "application/json" },
550
+ headers: {
551
+ Authorization: `Bearer ${this.config.apiKey}`,
552
+ "Content-Type": "application/json"
553
+ },
506
554
  body: JSON.stringify({
507
555
  token: data.token,
508
- password: data.password,
509
- project_id: this.config.projectId
556
+ password: data.password
510
557
  })
511
558
  }
512
559
  );
@@ -525,10 +572,12 @@ var AuthGateClient = class {
525
572
  `${this.config.baseUrl}/api/proxy/email/magic-link/send`,
526
573
  {
527
574
  method: "POST",
528
- headers: { "Content-Type": "application/json" },
575
+ headers: {
576
+ Authorization: `Bearer ${this.config.apiKey}`,
577
+ "Content-Type": "application/json"
578
+ },
529
579
  body: JSON.stringify({
530
580
  email: data.email,
531
- project_id: this.config.projectId,
532
581
  callback_url: data.callbackUrl
533
582
  })
534
583
  }
@@ -546,11 +595,13 @@ var AuthGateClient = class {
546
595
  async emailVerifyCode(data) {
547
596
  return fetch(`${this.config.baseUrl}/api/proxy/email/verify`, {
548
597
  method: "POST",
549
- headers: { "Content-Type": "application/json" },
598
+ headers: {
599
+ Authorization: `Bearer ${this.config.apiKey}`,
600
+ "Content-Type": "application/json"
601
+ },
550
602
  body: JSON.stringify({
551
603
  email: data.email,
552
- code: data.code,
553
- project_id: this.config.projectId
604
+ code: data.code
554
605
  })
555
606
  });
556
607
  }
@@ -566,10 +617,12 @@ var AuthGateClient = class {
566
617
  async smsSendCode(data) {
567
618
  return fetch(`${this.config.baseUrl}/api/proxy/sms/send-code`, {
568
619
  method: "POST",
569
- headers: { "Content-Type": "application/json" },
620
+ headers: {
621
+ Authorization: `Bearer ${this.config.apiKey}`,
622
+ "Content-Type": "application/json"
623
+ },
570
624
  body: JSON.stringify({
571
625
  phone: data.phone,
572
- project_id: this.config.projectId,
573
626
  callback_url: data.callbackUrl
574
627
  })
575
628
  });
@@ -583,11 +636,13 @@ var AuthGateClient = class {
583
636
  async smsVerifyCode(data) {
584
637
  return fetch(`${this.config.baseUrl}/api/proxy/sms/verify-code`, {
585
638
  method: "POST",
586
- headers: { "Content-Type": "application/json" },
639
+ headers: {
640
+ Authorization: `Bearer ${this.config.apiKey}`,
641
+ "Content-Type": "application/json"
642
+ },
587
643
  body: JSON.stringify({
588
644
  phone: data.phone,
589
645
  code: data.code,
590
- project_id: this.config.projectId,
591
646
  callback_url: data.callbackUrl
592
647
  })
593
648
  });
@@ -610,10 +665,12 @@ var AuthGateClient = class {
610
665
  `${this.config.baseUrl}/api/proxy/token/refresh`,
611
666
  {
612
667
  method: "POST",
613
- headers: { "Content-Type": "application/json" },
668
+ headers: {
669
+ Authorization: `Bearer ${this.config.apiKey}`,
670
+ "Content-Type": "application/json"
671
+ },
614
672
  body: JSON.stringify({
615
- refresh_token: refreshToken,
616
- project_id: this.config.projectId
673
+ refresh_token: refreshToken
617
674
  })
618
675
  }
619
676
  );
@@ -638,10 +695,12 @@ var AuthGateClient = class {
638
695
  `${this.config.baseUrl}/api/proxy/sessions/revoke`,
639
696
  {
640
697
  method: "POST",
641
- headers: { "Content-Type": "application/json" },
698
+ headers: {
699
+ Authorization: `Bearer ${this.config.apiKey}`,
700
+ "Content-Type": "application/json"
701
+ },
642
702
  body: JSON.stringify({
643
- refresh_token: refreshToken,
644
- project_id: this.config.projectId
703
+ refresh_token: refreshToken
645
704
  })
646
705
  }
647
706
  );
@@ -664,10 +723,11 @@ var AuthGateClient = class {
664
723
  async mfaVerify(data) {
665
724
  return fetch(`${this.config.baseUrl}/api/proxy/mfa/verify`, {
666
725
  method: "POST",
667
- headers: { "Content-Type": "application/json" },
668
- body: JSON.stringify(__spreadProps(__spreadValues({}, data), {
669
- project_id: this.config.projectId
670
- }))
726
+ headers: {
727
+ Authorization: `Bearer ${this.config.apiKey}`,
728
+ "Content-Type": "application/json"
729
+ },
730
+ body: JSON.stringify(data)
671
731
  });
672
732
  }
673
733
  /**
@@ -798,11 +858,11 @@ var AuthGateClient = class {
798
858
  async mfaSmsSendCode(challenge) {
799
859
  return fetch(`${this.config.baseUrl}/api/proxy/mfa/sms/send-code`, {
800
860
  method: "POST",
801
- headers: { "Content-Type": "application/json" },
802
- body: JSON.stringify({
803
- challenge,
804
- project_id: this.config.projectId
805
- })
861
+ headers: {
862
+ Authorization: `Bearer ${this.config.apiKey}`,
863
+ "Content-Type": "application/json"
864
+ },
865
+ body: JSON.stringify({ challenge })
806
866
  });
807
867
  }
808
868
  // ---------------------------------------------------------------------------
@@ -824,10 +884,6 @@ var AuthGateClient = class {
824
884
  get callbackPath() {
825
885
  return this.config.callbackPath;
826
886
  }
827
- /** The AuthGate project ID. */
828
- get projectId() {
829
- return this.config.projectId;
830
- }
831
887
  /** The AuthGate service base URL. */
832
888
  get baseUrl() {
833
889
  return this.config.baseUrl;
@@ -865,13 +921,78 @@ var AuthGateClient = class {
865
921
  path: "/"
866
922
  };
867
923
  }
924
+ // ---------------------------------------------------------------------------
925
+ // Config validation (async, called by framework factories)
926
+ // ---------------------------------------------------------------------------
927
+ /**
928
+ * Validate the SDK configuration against the AuthGate server.
929
+ *
930
+ * Checks that the API key is valid and (optionally) that a callback URL
931
+ * is registered for the project. This is called automatically by framework
932
+ * factories (`createAuthGate`, `createAuthRoutes`) and logs errors to the
933
+ * console — it does NOT throw.
934
+ *
935
+ * @param callbackUrl - The full callback URL to check registration for.
936
+ * @internal
937
+ */
938
+ async validateConfig(callbackUrl) {
939
+ try {
940
+ const response = await fetch(
941
+ `${this.config.baseUrl}/api/v1/config/validate`,
942
+ {
943
+ method: "POST",
944
+ headers: {
945
+ Authorization: `Bearer ${this.config.apiKey}`,
946
+ "Content-Type": "application/json"
947
+ },
948
+ body: JSON.stringify({
949
+ callback_url: callbackUrl
950
+ })
951
+ }
952
+ );
953
+ if (response.status === 401) {
954
+ console.error(
955
+ "\n[AuthGate] \u2717 Invalid API key.\n The API key you provided is not recognized or has been revoked.\n \u2192 Go to your AuthGate dashboard \u2192 Project \u2192 API Key to get a valid key.\n"
956
+ );
957
+ return;
958
+ }
959
+ if (!response.ok) {
960
+ console.warn(
961
+ "[AuthGate] Could not validate config (server returned " + response.status + "). Auth flows may fail."
962
+ );
963
+ return;
964
+ }
965
+ const data = await response.json();
966
+ if (callbackUrl && data.callback_url_registered === false) {
967
+ console.error(
968
+ "\n[AuthGate] \u2717 Callback URL not registered: " + callbackUrl + "\n This URL must be added to your project's allowed callback URLs.\n \u2192 Go to your AuthGate dashboard \u2192 Project \u2192 Callback URLs \u2192 Add URL.\n"
969
+ );
970
+ }
971
+ } catch (e) {
972
+ console.warn(
973
+ "[AuthGate] Could not reach AuthGate server at " + this.config.baseUrl + " to validate config. Auth flows may fail if the URL is wrong."
974
+ );
975
+ }
976
+ }
868
977
  };
869
978
  function createAuthGateClient(config) {
870
979
  return new AuthGateClient(config);
871
980
  }
981
+
982
+ // src/billing.ts
983
+ function hasPlan(entitlements, plan) {
984
+ if (!(entitlements == null ? void 0 : entitlements.plan)) return false;
985
+ return entitlements.plan.toLowerCase() === plan.toLowerCase();
986
+ }
987
+ function hasFeature(entitlements, feature) {
988
+ if (!(entitlements == null ? void 0 : entitlements.features)) return false;
989
+ const lower = feature.toLowerCase();
990
+ return entitlements.features.some((f) => f.toLowerCase() === lower);
991
+ }
872
992
  export {
873
993
  AuthGateClient,
874
994
  AuthGateError,
995
+ ConfigurationError,
875
996
  DEFAULT_COOKIE_NAME,
876
997
  InvalidStateError,
877
998
  NONCE_COOKIE_NAME,
@@ -881,5 +1002,7 @@ export {
881
1002
  SUPPORTED_PROVIDERS,
882
1003
  SessionDecryptionError,
883
1004
  TokenVerificationError,
884
- createAuthGateClient
1005
+ createAuthGateClient,
1006
+ hasFeature,
1007
+ hasPlan
885
1008
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auth-gate/core",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {