@yappr/server-node 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @yappr/server-node
2
+
3
+ Mint short-lived Yappr end-user auth tokens **from your backend**. Runs on Node 18+, Bun, and edge runtimes (pure WebCrypto, zero dependencies).
4
+
5
+ ## Why
6
+
7
+ Your **publishable key** (`pk_live_…`) identifies your tenant and is safe to ship in the browser. To prove *who* an end-user is, your server signs a short-lived token with your **secret** (`jwt_secret`) — the secret never leaves your backend, so the browser can't impersonate anyone.
8
+
9
+ ## Install
10
+
11
+ npm install @yappr/server-node
12
+
13
+ ## Mint a token (server-side)
14
+
15
+ ```ts
16
+ import { mintToken } from "@yappr/server-node";
17
+
18
+ // In your authenticated API route, for the currently logged-in user:
19
+ const token = await mintToken(
20
+ { tenantId: "t_xxx", userId: user.id, displayName: user.name },
21
+ process.env.YAPPR_JWT_SECRET!, // your tenant's jwt_secret — server-only
22
+ { expiresInSeconds: 3600 },
23
+ );
24
+ // Return `token` to the browser; pass it to createClient({ key: pk_live, token }).
25
+ ```
26
+
27
+ `tenantId` and your `pk_live` / `jwt_secret` are issued when your Yappr tenant is provisioned. Keep `jwt_secret` secret (env var / secrets manager) — treat it like a password.
28
+
29
+ ## API
30
+
31
+ - `mintToken(claims, secret, opts?)` → `Promise<string>` — `claims: { tenantId, userId, displayName? }`; `opts: { expiresInSeconds?: number (default 3600) }`.
32
+ - `verifyToken(token, secret, opts?)` → `Promise<VerifiedToken>` — usually only the Yappr server calls this; exposed for testing.
@@ -0,0 +1,2 @@
1
+ export declare function base64urlEncode(bytes: Uint8Array): string;
2
+ export declare function base64urlDecode(s: string): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,14 @@
1
+ export function base64urlEncode(bytes) {
2
+ let bin = "";
3
+ for (const b of bytes)
4
+ bin += String.fromCharCode(b);
5
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6
+ }
7
+ export function base64urlDecode(s) {
8
+ const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (s.length % 4)) % 4);
9
+ const bin = atob(b64);
10
+ const out = new Uint8Array(bin.length);
11
+ for (let i = 0; i < bin.length; i++)
12
+ out[i] = bin.charCodeAt(i);
13
+ return out;
14
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./base64url.js";
2
+ export * from "./jwt.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./base64url.js";
2
+ export * from "./jwt.js";
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export interface YapprClaims {
2
+ tenantId: string;
3
+ userId: string;
4
+ displayName?: string;
5
+ }
6
+ export interface VerifiedToken extends YapprClaims {
7
+ iat: number;
8
+ exp: number;
9
+ }
10
+ export declare function mintToken(claims: YapprClaims, secret: string, opts?: {
11
+ expiresInSeconds?: number;
12
+ now?: number;
13
+ }): Promise<string>;
14
+ export declare function verifyToken(token: string, secret: string, opts?: {
15
+ now?: number;
16
+ }): Promise<VerifiedToken>;
package/dist/jwt.js ADDED
@@ -0,0 +1,52 @@
1
+ import { base64urlDecode, base64urlEncode } from "./base64url.js";
2
+ const enc = new TextEncoder();
3
+ const dec = new TextDecoder();
4
+ function b64urlJson(obj) {
5
+ return base64urlEncode(enc.encode(JSON.stringify(obj)));
6
+ }
7
+ async function hmacKey(secret) {
8
+ return crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
9
+ }
10
+ export async function mintToken(claims, secret, opts = {}) {
11
+ const now = opts.now ?? Math.floor(Date.now() / 1000);
12
+ const exp = now + (opts.expiresInSeconds ?? 3600);
13
+ const header = b64urlJson({ alg: "HS256", typ: "JWT" });
14
+ const payload = b64urlJson({ ...claims, iat: now, exp });
15
+ const signingInput = `${header}.${payload}`;
16
+ const sig = await crypto.subtle.sign("HMAC", await hmacKey(secret), enc.encode(signingInput));
17
+ return `${signingInput}.${base64urlEncode(new Uint8Array(sig))}`;
18
+ }
19
+ export async function verifyToken(token, secret, opts = {}) {
20
+ const parts = token.split(".");
21
+ if (parts.length !== 3)
22
+ throw new Error("token_invalid");
23
+ const [header, payload, sig] = parts;
24
+ let headerObj;
25
+ try {
26
+ headerObj = JSON.parse(dec.decode(base64urlDecode(header)));
27
+ }
28
+ catch {
29
+ throw new Error("token_invalid");
30
+ }
31
+ if (headerObj.alg !== "HS256")
32
+ throw new Error("token_invalid");
33
+ if (headerObj.typ !== undefined && headerObj.typ !== "JWT")
34
+ throw new Error("token_invalid");
35
+ const ok = await crypto.subtle.verify("HMAC", await hmacKey(secret), base64urlDecode(sig), enc.encode(`${header}.${payload}`));
36
+ if (!ok)
37
+ throw new Error("token_invalid");
38
+ let claims;
39
+ try {
40
+ claims = JSON.parse(dec.decode(base64urlDecode(payload)));
41
+ }
42
+ catch {
43
+ throw new Error("token_invalid");
44
+ }
45
+ if (typeof claims.tenantId !== "string" || typeof claims.userId !== "string" || typeof claims.exp !== "number") {
46
+ throw new Error("token_invalid");
47
+ }
48
+ const now = opts.now ?? Math.floor(Date.now() / 1000);
49
+ if (claims.exp <= now)
50
+ throw new Error("token_expired");
51
+ return claims;
52
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@yappr/server-node",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Mint short-lived Yappr end-user auth tokens from your backend (Node 18+, Bun, edge).",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "5.9.3",
16
+ "vitest": "^3.0.0"
17
+ },
18
+ "scripts": {
19
+ "check": "tsc --noEmit -p tsconfig.json",
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "test": "vitest run"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "default": "./dist/index.js"
28
+ }
29
+ }
30
+ }