@authn-sh/sdk-node 0.4.0-alpha.1

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 ADDED
@@ -0,0 +1,117 @@
1
+ # @authn-sh/sdk-node
2
+
3
+ Node.js / Edge-runtime backend SDK for [authn.sh](https://authn.sh).
4
+
5
+ Mirrors [`@authn-sh/sdk-php`](https://github.com/authn-sh/sdk-php) — BAPI resource managers, session-JWT verification, webhook signature verification. The frontend counterpart is [`@authn-sh/sdk-js`](https://github.com/authn-sh/javascript/tree/main/packages/sdk-js).
6
+
7
+ Runs anywhere `fetch` exists: Node 18+, Cloudflare Workers, Vercel Edge, Bun, Deno.
8
+
9
+ ## Install
10
+
11
+ ```
12
+ npm install @authn-sh/sdk-node
13
+ ```
14
+
15
+ ## BAPI client
16
+
17
+ ```ts
18
+ import { Authn } from '@authn-sh/sdk-node';
19
+
20
+ const authn = new Authn({ secretKey: process.env.AUTHN_SECRET_KEY! });
21
+
22
+ // Users
23
+ const user = await authn.users.get('user_01HXYZ');
24
+ await authn.users.ban('user_01HXYZ');
25
+ const fresh = await authn.users.create({ first_name: 'Alice', email_addresses: ['alice@example.com'] });
26
+
27
+ // Sessions
28
+ const session = await authn.sessions.get('sess_01HXYZ');
29
+ const jwt = await authn.sessions.getToken('sess_01HXYZ', 'my-jwt-template');
30
+
31
+ // Organizations + nested managers
32
+ const org = await authn.organizations.create({ name: 'Acme', slug: 'acme' });
33
+ await authn.organizations.members(org.id).create({ userId: user.id, role: 'org:admin' });
34
+ await authn.organizations.invitations(org.id).create({ email_address: 'bob@acme.com', role: 'org:member' });
35
+ await authn.organizations.domains(org.id).create('acme.com', 'automatic_invitation');
36
+
37
+ // Social providers + phone numbers + external accounts + SMS templates
38
+ await authn.oauthProviders.list();
39
+ await authn.phoneNumbers.list({ userId: user.id } as any);
40
+ await authn.externalAccounts.list({ userId: user.id } as any);
41
+ await authn.smsTemplates.get('verification_code');
42
+
43
+ // Instance settings
44
+ const instance = await authn.instance.get();
45
+ await authn.instance.update({ multi_factor: { phone_code: { enabled: true } } });
46
+ ```
47
+
48
+ Errors from the API surface as `AuthnHttpError` (`status`, `code`, `requestId`, `errors[]`).
49
+
50
+ ## Session-JWT verification
51
+
52
+ ```ts
53
+ import { TokenVerifier } from '@authn-sh/sdk-node';
54
+
55
+ const verifier = new TokenVerifier({ publishableKey: process.env.AUTHN_PUBLISHABLE_KEY! });
56
+
57
+ // In your auth middleware:
58
+ const cookie = req.cookies['__session'];
59
+ const claims = await verifier.verify(cookie); // throws AuthnTokenInvalidError on bad token
60
+
61
+ console.log(claims.sub); // user_…
62
+ console.log(claims.organization?.role); // 'org:admin'
63
+ console.log(claims.hasPermission('org:billing:read'));
64
+ console.log(claims.hasVerifiedPhoneNumber());
65
+ console.log(claims.preferredSecondFactor()); // 'totp' | 'phone_code' | 'backup_code' | null
66
+ ```
67
+
68
+ For "best-effort" auth that falls back to unauthenticated, use `tryVerify()` — returns `null` instead of throwing.
69
+
70
+ The verifier resolves the FAPI host from the `publishableKey`; pass `frontendApiUrl` explicitly when self-hosting on a custom domain. JWKS is fetched once and cached in memory (default 10 min TTL).
71
+
72
+ ## Webhook signature verification
73
+
74
+ ```ts
75
+ import express from 'express';
76
+ import { WebhookSignatureVerifier } from '@authn-sh/sdk-node';
77
+
78
+ const app = express();
79
+ const verifier = new WebhookSignatureVerifier({
80
+ signingSecret: process.env.AUTHN_WEBHOOK_SECRET!,
81
+ });
82
+
83
+ app.post('/webhooks/authn', express.raw({ type: 'application/json' }), (req, res) => {
84
+ try {
85
+ const event = verifier.verify(req.body.toString('utf8'), req.headers);
86
+ switch (event.type) {
87
+ case 'user.created': /* … */; break;
88
+ case 'phoneNumber.verified': /* … */; break;
89
+ }
90
+ res.status(204).end();
91
+ } catch {
92
+ res.status(400).end();
93
+ }
94
+ });
95
+ ```
96
+
97
+ To rotate the signing secret without downtime, pass an array — the verifier accepts a request if any provided signature matches any secret:
98
+
99
+ ```ts
100
+ new WebhookSignatureVerifier({ signingSecret: [oldSecret, newSecret] });
101
+ ```
102
+
103
+ ## Runtime support
104
+
105
+ | Runtime | Status |
106
+ | ----------------- | -------------------------------------------- |
107
+ | Node.js 18+ | ✓ first-class |
108
+ | Cloudflare Workers | ✓ (uses `globalThis.fetch` + `node:crypto`) |
109
+ | Vercel Edge | ✓ |
110
+ | Bun | ✓ |
111
+ | Deno | ✓ via `npm:` specifier |
112
+
113
+ The webhook verifier imports `node:crypto`. On edge runtimes that polyfill it (Workers, Vercel Edge), no action is needed.
114
+
115
+ ## License
116
+
117
+ AGPL-3.0-only — see [LICENSE](./LICENSE). For commercially licensed deployments, contact [authn.sh](https://authn.sh).
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ // src/errors/index.ts
4
+ var AuthnHttpError = class extends Error {
5
+ constructor(status, errors, requestId, message) {
6
+ super(message ?? (errors[0]?.longMessage || errors[0]?.message) ?? `authn.sh API returned HTTP ${status}`);
7
+ this.status = status;
8
+ this.errors = errors;
9
+ this.requestId = requestId;
10
+ }
11
+ status;
12
+ errors;
13
+ requestId;
14
+ name = "AuthnHttpError";
15
+ /** The primary error code returned by the API (when present). */
16
+ get code() {
17
+ return this.errors[0]?.code ?? null;
18
+ }
19
+ };
20
+ var AuthnConfigError = class extends Error {
21
+ name = "AuthnConfigError";
22
+ };
23
+ var AuthnTokenInvalidError = class extends Error {
24
+ constructor(reason, message) {
25
+ super(message ?? `Token invalid: ${reason}`);
26
+ this.reason = reason;
27
+ }
28
+ reason;
29
+ name = "AuthnTokenInvalidError";
30
+ };
31
+ var AuthnWebhookSignatureInvalidError = class extends Error {
32
+ constructor(reason, message) {
33
+ super(message ?? `Webhook signature invalid: ${reason}`);
34
+ this.reason = reason;
35
+ }
36
+ reason;
37
+ name = "AuthnWebhookSignatureInvalidError";
38
+ };
39
+
40
+ exports.AuthnConfigError = AuthnConfigError;
41
+ exports.AuthnHttpError = AuthnHttpError;
42
+ exports.AuthnTokenInvalidError = AuthnTokenInvalidError;
43
+ exports.AuthnWebhookSignatureInvalidError = AuthnWebhookSignatureInvalidError;
44
+ //# sourceMappingURL=errors.cjs.map
45
+ //# sourceMappingURL=errors.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/index.ts"],"names":[],"mappings":";;;AAaO,IAAM,cAAA,GAAN,cAA6B,KAAA,CAAM;AAAA,EAGxC,WAAA,CACkB,MAAA,EACA,MAAA,EACA,SAAA,EAChB,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAA,KAAY,MAAA,CAAO,CAAC,CAAA,EAAG,WAAA,IAAe,MAAA,CAAO,CAAC,CAAA,EAAG,OAAA,CAAA,IAAY,CAAA,2BAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AALzF,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAAA,EAIlB;AAAA,EANkB,MAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EALO,IAAA,GAAM,gBAAA;AAAA;AAAA,EAY/B,IAAI,IAAA,GAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,EAAG,IAAA,IAAQ,IAAA;AAAA,EACjC;AACF;AAKO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EACjB,IAAA,GAAM,kBAAA;AACjC;AAMO,IAAM,sBAAA,GAAN,cAAqC,KAAA,CAAM;AAAA,EAGhD,WAAA,CACkB,QAShB,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAA,IAAW,CAAA,eAAA,EAAkB,MAAM,CAAA,CAAE,CAAA;AAX3B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAYlB;AAAA,EAZkB,MAAA;AAAA,EAHO,IAAA,GAAM,wBAAA;AAgBjC;AAKO,IAAM,iCAAA,GAAN,cAAgD,KAAA,CAAM;AAAA,EAG3D,WAAA,CACkB,QAMhB,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAA,IAAW,CAAA,2BAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AARvC,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EASlB;AAAA,EATkB,MAAA;AAAA,EAHO,IAAA,GAAM,mCAAA;AAajC","file":"errors.cjs","sourcesContent":["/**\n * Error shape emitted by the authn.sh API.\n */\nexport interface AuthnApiError {\n code: string;\n message: string;\n longMessage?: string;\n meta?: Record<string, unknown>;\n}\n\n/**\n * Thrown when a request to the BAPI returns a non-2xx response.\n */\nexport class AuthnHttpError extends Error {\n public override readonly name ='AuthnHttpError';\n\n constructor(\n public readonly status: number,\n public readonly errors: AuthnApiError[],\n public readonly requestId: string | null,\n message?: string,\n ) {\n super(message ?? (errors[0]?.longMessage || errors[0]?.message) ?? `authn.sh API returned HTTP ${status}`);\n }\n\n /** The primary error code returned by the API (when present). */\n get code(): string | null {\n return this.errors[0]?.code ?? null;\n }\n}\n\n/**\n * Thrown when the SDK is misconfigured (missing secret key, malformed URL, etc.).\n */\nexport class AuthnConfigError extends Error {\n public override readonly name ='AuthnConfigError';\n}\n\n/**\n * Thrown when a JWT can't be verified — wrong signature, expired, malformed,\n * issuer mismatch, etc. The `reason` discriminates between the failure modes.\n */\nexport class AuthnTokenInvalidError extends Error {\n public override readonly name ='AuthnTokenInvalidError';\n\n constructor(\n public readonly reason:\n | 'malformed'\n | 'signature_invalid'\n | 'expired'\n | 'not_yet_valid'\n | 'issuer_mismatch'\n | 'audience_mismatch'\n | 'key_not_found'\n | 'jwks_fetch_failed',\n message?: string,\n ) {\n super(message ?? `Token invalid: ${reason}`);\n }\n}\n\n/**\n * Thrown when a webhook signature can't be verified.\n */\nexport class AuthnWebhookSignatureInvalidError extends Error {\n public override readonly name ='AuthnWebhookSignatureInvalidError';\n\n constructor(\n public readonly reason:\n | 'missing_signature_header'\n | 'malformed_signature_header'\n | 'no_matching_signature'\n | 'timestamp_too_old'\n | 'timestamp_missing',\n message?: string,\n ) {\n super(message ?? `Webhook signature invalid: ${reason}`);\n }\n}\n"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Error shape emitted by the authn.sh API.
3
+ */
4
+ interface AuthnApiError {
5
+ code: string;
6
+ message: string;
7
+ longMessage?: string;
8
+ meta?: Record<string, unknown>;
9
+ }
10
+ /**
11
+ * Thrown when a request to the BAPI returns a non-2xx response.
12
+ */
13
+ declare class AuthnHttpError extends Error {
14
+ readonly status: number;
15
+ readonly errors: AuthnApiError[];
16
+ readonly requestId: string | null;
17
+ readonly name = "AuthnHttpError";
18
+ constructor(status: number, errors: AuthnApiError[], requestId: string | null, message?: string);
19
+ /** The primary error code returned by the API (when present). */
20
+ get code(): string | null;
21
+ }
22
+ /**
23
+ * Thrown when the SDK is misconfigured (missing secret key, malformed URL, etc.).
24
+ */
25
+ declare class AuthnConfigError extends Error {
26
+ readonly name = "AuthnConfigError";
27
+ }
28
+ /**
29
+ * Thrown when a JWT can't be verified — wrong signature, expired, malformed,
30
+ * issuer mismatch, etc. The `reason` discriminates between the failure modes.
31
+ */
32
+ declare class AuthnTokenInvalidError extends Error {
33
+ readonly reason: 'malformed' | 'signature_invalid' | 'expired' | 'not_yet_valid' | 'issuer_mismatch' | 'audience_mismatch' | 'key_not_found' | 'jwks_fetch_failed';
34
+ readonly name = "AuthnTokenInvalidError";
35
+ constructor(reason: 'malformed' | 'signature_invalid' | 'expired' | 'not_yet_valid' | 'issuer_mismatch' | 'audience_mismatch' | 'key_not_found' | 'jwks_fetch_failed', message?: string);
36
+ }
37
+ /**
38
+ * Thrown when a webhook signature can't be verified.
39
+ */
40
+ declare class AuthnWebhookSignatureInvalidError extends Error {
41
+ readonly reason: 'missing_signature_header' | 'malformed_signature_header' | 'no_matching_signature' | 'timestamp_too_old' | 'timestamp_missing';
42
+ readonly name = "AuthnWebhookSignatureInvalidError";
43
+ constructor(reason: 'missing_signature_header' | 'malformed_signature_header' | 'no_matching_signature' | 'timestamp_too_old' | 'timestamp_missing', message?: string);
44
+ }
45
+
46
+ export { type AuthnApiError, AuthnConfigError, AuthnHttpError, AuthnTokenInvalidError, AuthnWebhookSignatureInvalidError };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Error shape emitted by the authn.sh API.
3
+ */
4
+ interface AuthnApiError {
5
+ code: string;
6
+ message: string;
7
+ longMessage?: string;
8
+ meta?: Record<string, unknown>;
9
+ }
10
+ /**
11
+ * Thrown when a request to the BAPI returns a non-2xx response.
12
+ */
13
+ declare class AuthnHttpError extends Error {
14
+ readonly status: number;
15
+ readonly errors: AuthnApiError[];
16
+ readonly requestId: string | null;
17
+ readonly name = "AuthnHttpError";
18
+ constructor(status: number, errors: AuthnApiError[], requestId: string | null, message?: string);
19
+ /** The primary error code returned by the API (when present). */
20
+ get code(): string | null;
21
+ }
22
+ /**
23
+ * Thrown when the SDK is misconfigured (missing secret key, malformed URL, etc.).
24
+ */
25
+ declare class AuthnConfigError extends Error {
26
+ readonly name = "AuthnConfigError";
27
+ }
28
+ /**
29
+ * Thrown when a JWT can't be verified — wrong signature, expired, malformed,
30
+ * issuer mismatch, etc. The `reason` discriminates between the failure modes.
31
+ */
32
+ declare class AuthnTokenInvalidError extends Error {
33
+ readonly reason: 'malformed' | 'signature_invalid' | 'expired' | 'not_yet_valid' | 'issuer_mismatch' | 'audience_mismatch' | 'key_not_found' | 'jwks_fetch_failed';
34
+ readonly name = "AuthnTokenInvalidError";
35
+ constructor(reason: 'malformed' | 'signature_invalid' | 'expired' | 'not_yet_valid' | 'issuer_mismatch' | 'audience_mismatch' | 'key_not_found' | 'jwks_fetch_failed', message?: string);
36
+ }
37
+ /**
38
+ * Thrown when a webhook signature can't be verified.
39
+ */
40
+ declare class AuthnWebhookSignatureInvalidError extends Error {
41
+ readonly reason: 'missing_signature_header' | 'malformed_signature_header' | 'no_matching_signature' | 'timestamp_too_old' | 'timestamp_missing';
42
+ readonly name = "AuthnWebhookSignatureInvalidError";
43
+ constructor(reason: 'missing_signature_header' | 'malformed_signature_header' | 'no_matching_signature' | 'timestamp_too_old' | 'timestamp_missing', message?: string);
44
+ }
45
+
46
+ export { type AuthnApiError, AuthnConfigError, AuthnHttpError, AuthnTokenInvalidError, AuthnWebhookSignatureInvalidError };
package/dist/errors.js ADDED
@@ -0,0 +1,40 @@
1
+ // src/errors/index.ts
2
+ var AuthnHttpError = class extends Error {
3
+ constructor(status, errors, requestId, message) {
4
+ super(message ?? (errors[0]?.longMessage || errors[0]?.message) ?? `authn.sh API returned HTTP ${status}`);
5
+ this.status = status;
6
+ this.errors = errors;
7
+ this.requestId = requestId;
8
+ }
9
+ status;
10
+ errors;
11
+ requestId;
12
+ name = "AuthnHttpError";
13
+ /** The primary error code returned by the API (when present). */
14
+ get code() {
15
+ return this.errors[0]?.code ?? null;
16
+ }
17
+ };
18
+ var AuthnConfigError = class extends Error {
19
+ name = "AuthnConfigError";
20
+ };
21
+ var AuthnTokenInvalidError = class extends Error {
22
+ constructor(reason, message) {
23
+ super(message ?? `Token invalid: ${reason}`);
24
+ this.reason = reason;
25
+ }
26
+ reason;
27
+ name = "AuthnTokenInvalidError";
28
+ };
29
+ var AuthnWebhookSignatureInvalidError = class extends Error {
30
+ constructor(reason, message) {
31
+ super(message ?? `Webhook signature invalid: ${reason}`);
32
+ this.reason = reason;
33
+ }
34
+ reason;
35
+ name = "AuthnWebhookSignatureInvalidError";
36
+ };
37
+
38
+ export { AuthnConfigError, AuthnHttpError, AuthnTokenInvalidError, AuthnWebhookSignatureInvalidError };
39
+ //# sourceMappingURL=errors.js.map
40
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/index.ts"],"names":[],"mappings":";AAaO,IAAM,cAAA,GAAN,cAA6B,KAAA,CAAM;AAAA,EAGxC,WAAA,CACkB,MAAA,EACA,MAAA,EACA,SAAA,EAChB,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAA,KAAY,MAAA,CAAO,CAAC,CAAA,EAAG,WAAA,IAAe,MAAA,CAAO,CAAC,CAAA,EAAG,OAAA,CAAA,IAAY,CAAA,2BAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AALzF,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAAA,EAIlB;AAAA,EANkB,MAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EALO,IAAA,GAAM,gBAAA;AAAA;AAAA,EAY/B,IAAI,IAAA,GAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,EAAG,IAAA,IAAQ,IAAA;AAAA,EACjC;AACF;AAKO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EACjB,IAAA,GAAM,kBAAA;AACjC;AAMO,IAAM,sBAAA,GAAN,cAAqC,KAAA,CAAM;AAAA,EAGhD,WAAA,CACkB,QAShB,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAA,IAAW,CAAA,eAAA,EAAkB,MAAM,CAAA,CAAE,CAAA;AAX3B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAYlB;AAAA,EAZkB,MAAA;AAAA,EAHO,IAAA,GAAM,wBAAA;AAgBjC;AAKO,IAAM,iCAAA,GAAN,cAAgD,KAAA,CAAM;AAAA,EAG3D,WAAA,CACkB,QAMhB,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAA,IAAW,CAAA,2BAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AARvC,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EASlB;AAAA,EATkB,MAAA;AAAA,EAHO,IAAA,GAAM,mCAAA;AAajC","file":"errors.js","sourcesContent":["/**\n * Error shape emitted by the authn.sh API.\n */\nexport interface AuthnApiError {\n code: string;\n message: string;\n longMessage?: string;\n meta?: Record<string, unknown>;\n}\n\n/**\n * Thrown when a request to the BAPI returns a non-2xx response.\n */\nexport class AuthnHttpError extends Error {\n public override readonly name ='AuthnHttpError';\n\n constructor(\n public readonly status: number,\n public readonly errors: AuthnApiError[],\n public readonly requestId: string | null,\n message?: string,\n ) {\n super(message ?? (errors[0]?.longMessage || errors[0]?.message) ?? `authn.sh API returned HTTP ${status}`);\n }\n\n /** The primary error code returned by the API (when present). */\n get code(): string | null {\n return this.errors[0]?.code ?? null;\n }\n}\n\n/**\n * Thrown when the SDK is misconfigured (missing secret key, malformed URL, etc.).\n */\nexport class AuthnConfigError extends Error {\n public override readonly name ='AuthnConfigError';\n}\n\n/**\n * Thrown when a JWT can't be verified — wrong signature, expired, malformed,\n * issuer mismatch, etc. The `reason` discriminates between the failure modes.\n */\nexport class AuthnTokenInvalidError extends Error {\n public override readonly name ='AuthnTokenInvalidError';\n\n constructor(\n public readonly reason:\n | 'malformed'\n | 'signature_invalid'\n | 'expired'\n | 'not_yet_valid'\n | 'issuer_mismatch'\n | 'audience_mismatch'\n | 'key_not_found'\n | 'jwks_fetch_failed',\n message?: string,\n ) {\n super(message ?? `Token invalid: ${reason}`);\n }\n}\n\n/**\n * Thrown when a webhook signature can't be verified.\n */\nexport class AuthnWebhookSignatureInvalidError extends Error {\n public override readonly name ='AuthnWebhookSignatureInvalidError';\n\n constructor(\n public readonly reason:\n | 'missing_signature_header'\n | 'malformed_signature_header'\n | 'no_matching_signature'\n | 'timestamp_too_old'\n | 'timestamp_missing',\n message?: string,\n ) {\n super(message ?? `Webhook signature invalid: ${reason}`);\n }\n}\n"]}