ppussh 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.
Files changed (46) hide show
  1. package/README.md +192 -0
  2. package/dist/accounts/index.d.ts +4 -0
  3. package/dist/accounts/index.d.ts.map +1 -0
  4. package/dist/accounts/index.js +8 -0
  5. package/dist/accounts/index.js.map +1 -0
  6. package/dist/accounts/namespace.d.ts +164 -0
  7. package/dist/accounts/namespace.d.ts.map +1 -0
  8. package/dist/accounts/namespace.js +293 -0
  9. package/dist/accounts/namespace.js.map +1 -0
  10. package/dist/accounts/types.d.ts +81 -0
  11. package/dist/accounts/types.d.ts.map +1 -0
  12. package/dist/accounts/types.js +14 -0
  13. package/dist/accounts/types.js.map +1 -0
  14. package/dist/client.d.ts +67 -0
  15. package/dist/client.d.ts.map +1 -0
  16. package/dist/client.js +81 -0
  17. package/dist/client.js.map +1 -0
  18. package/dist/errors.d.ts +81 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +94 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/http.d.ts +30 -0
  23. package/dist/http.d.ts.map +1 -0
  24. package/dist/http.js +169 -0
  25. package/dist/http.js.map +1 -0
  26. package/dist/index.d.ts +47 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +58 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/payments/index.d.ts +3 -0
  31. package/dist/payments/index.d.ts.map +1 -0
  32. package/dist/payments/index.js +6 -0
  33. package/dist/payments/index.js.map +1 -0
  34. package/dist/payments/namespace.d.ts +146 -0
  35. package/dist/payments/namespace.d.ts.map +1 -0
  36. package/dist/payments/namespace.js +229 -0
  37. package/dist/payments/namespace.js.map +1 -0
  38. package/dist/payments/types.d.ts +98 -0
  39. package/dist/payments/types.d.ts.map +1 -0
  40. package/dist/payments/types.js +10 -0
  41. package/dist/payments/types.js.map +1 -0
  42. package/dist/webhooks.d.ts +56 -0
  43. package/dist/webhooks.d.ts.map +1 -0
  44. package/dist/webhooks.js +67 -0
  45. package/dist/webhooks.js.map +1 -0
  46. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # ppussh
2
+
3
+ Official TypeScript/JavaScript SDK for the [PPUSSH](https://ppussh.com) platform —
4
+ Accounts (OIDC / OAuth 2.0) and Payments in a single client.
5
+
6
+ ## Requirements
7
+
8
+ - Node.js 18+
9
+ - An Accounts **clientId** and **clientSecret** (obtain from the Accounts admin console)
10
+ - A running instance of the Accounts and Payments services
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install ppussh
16
+ ```
17
+
18
+ ## Configuration
19
+
20
+ The SDK requires the base URLs for both services. Set them via environment
21
+ variables (recommended for production) or pass them directly to the constructor.
22
+
23
+ | Environment variable | Purpose |
24
+ | ----------------------- | ------------------------------ |
25
+ | `PPUSSH_ACCOUNTS_URL` | Base URL of the Accounts API |
26
+ | `PPUSSH_PAYMENTS_URL` | Base URL of the Payments API |
27
+
28
+ ```bash
29
+ export PPUSSH_ACCOUNTS_URL="https://accounts.example.com"
30
+ export PPUSSH_PAYMENTS_URL="https://payments.example.com"
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ ```ts
36
+ import { PpusshClient } from "ppussh";
37
+
38
+ // URLs are read from PPUSSH_ACCOUNTS_URL / PPUSSH_PAYMENTS_URL env vars,
39
+ // or pass them explicitly:
40
+ const client = new PpusshClient({
41
+ clientId: "your-product-client-id",
42
+ clientSecret: "your-product-client-secret",
43
+ paymentsAdminKey: "your-payments-admin-key", // optional; needed for admin calls
44
+ // accountsUrl: "https://accounts.example.com", // or set PPUSSH_ACCOUNTS_URL
45
+ // paymentsUrl: "https://payments.example.com", // or set PPUSSH_PAYMENTS_URL
46
+ });
47
+ ```
48
+
49
+ ### OIDC callback (Express example)
50
+
51
+ ```ts
52
+ import express from "express";
53
+ import { PpusshClient } from "ppussh";
54
+
55
+ const app = express();
56
+ const client = new PpusshClient({ clientId: "...", clientSecret: "..." });
57
+
58
+ const REDIRECT_URI = "https://yourapp.example.com/auth/callback";
59
+
60
+ app.get("/auth/callback", async (req, res) => {
61
+ const { code } = req.query as { code: string };
62
+ const token = await client.accounts.exchangeCode(code, REDIRECT_URI);
63
+ // token.user contains the authenticated user's profile
64
+ res.json({ userId: token.user.id, email: token.user.email });
65
+ });
66
+ ```
67
+
68
+ ### Token verification middleware
69
+
70
+ ```ts
71
+ import { PpusshClient, PpusshAuthError } from "ppussh";
72
+
73
+ const client = new PpusshClient({ clientId: "...", clientSecret: "..." });
74
+
75
+ async function requireAuth(req: Request): Promise<string> {
76
+ const auth = req.headers.get("authorization") ?? "";
77
+ if (!auth.startsWith("Bearer ")) throw new Response(null, { status: 401 });
78
+ const bearer = auth.slice(7);
79
+ try {
80
+ const result = await client.accounts.verifyToken(bearer);
81
+ return result.userId;
82
+ } catch (err) {
83
+ if (err instanceof PpusshAuthError) throw new Response(null, { status: 401 });
84
+ throw err;
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### Token refresh
90
+
91
+ ```ts
92
+ // Uses the refresh token stored internally after exchangeCode()
93
+ const newToken = await client.accounts.refresh();
94
+
95
+ // Or pass an explicit refresh token:
96
+ const newToken = await client.accounts.refresh("rt_...");
97
+ ```
98
+
99
+ ### Logout
100
+
101
+ ```ts
102
+ await client.accounts.logout(); // uses stored refresh token
103
+ ```
104
+
105
+ ### Billing — create a customer and subscription
106
+
107
+ ```ts
108
+ import { randomUUID } from "crypto";
109
+
110
+ // Create or retrieve a customer record
111
+ const customer = await client.payments.createCustomer(token.user.id, {
112
+ workspaceId: "ws-123", // optional
113
+ });
114
+
115
+ // List available plans for a product
116
+ const plans = await client.payments.listPlans("prod-abc");
117
+
118
+ // Subscribe the customer
119
+ const subscription = await client.payments.createSubscription({
120
+ customerId: customer.id,
121
+ paymentProductId: "prod-abc",
122
+ planKey: "pro",
123
+ idempotencyKey: randomUUID(),
124
+ });
125
+ ```
126
+
127
+ ## Error handling
128
+
129
+ All exceptions are subclasses of `PpusshError`:
130
+
131
+ ```ts
132
+ import {
133
+ PpusshError, // base class
134
+ PpusshAuthError, // 401 — invalid or expired token / credentials
135
+ PpusshConsentRequired, // 403 — user hasn't consented to this product's scopes
136
+ PpusshPaymentError, // non-2xx from the Payments service
137
+ PpusshNetworkError, // all retries exhausted / connection failure
138
+ } from "ppussh";
139
+
140
+ try {
141
+ const token = await client.accounts.exchangeCode(code, REDIRECT_URI);
142
+ } catch (err) {
143
+ if (err instanceof PpusshConsentRequired) {
144
+ // Redirect the user to the consent flow
145
+ redirectToConsent(err.clientId, err.productName);
146
+ } else if (err instanceof PpusshAuthError) {
147
+ // Invalid code or expired credentials
148
+ } else if (err instanceof PpusshNetworkError) {
149
+ // Retry later
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### Retry policy
155
+
156
+ | Condition | Behaviour |
157
+ | ----------------------- | -------------------------------------------------------- |
158
+ | 5xx / network error | Up to 3 attempts, exponential backoff (0.5 s, 1 s, 2 s) |
159
+ | 429 Too Many Requests | Respects `Retry-After` header, max 2 retries |
160
+ | 4xx (not 429) | Never retried — raises immediately |
161
+
162
+ ## API reference
163
+
164
+ ### `client.accounts`
165
+
166
+ | Method | Description |
167
+ | ------ | ----------- |
168
+ | `exchangeCode(code, redirectUri)` | Exchange an auth code for tokens (OIDC callback) |
169
+ | `refresh(refreshToken?)` | Refresh the access token |
170
+ | `verifyToken(accessToken)` | Validate an incoming bearer token (use in middleware) |
171
+ | `logout(refreshToken?)` | Revoke the session |
172
+ | `getUser(accessToken?)` | Fetch the authenticated user's profile |
173
+ | `getEntitlements(accessToken?)` | List the user's product entitlements |
174
+ | `getSessions(accessToken?)` | List the user's active sessions |
175
+
176
+ ### `client.payments`
177
+
178
+ | Method | Description |
179
+ | ------ | ----------- |
180
+ | `createCustomer(ownerUserId, opts?)` | Create or retrieve a customer record |
181
+ | `getCustomer(customerId)` | Fetch a customer by ID |
182
+ | `createSubscription(opts)` | Create a subscription |
183
+ | `listSubscriptions(customerId, opts?)` | List subscriptions for a customer |
184
+ | `getSubscription(subscriptionId)` | Fetch a subscription by ID |
185
+ | `cancelSubscription(subscriptionId, opts?)` | Cancel a subscription |
186
+ | `listPlans(paymentProductId)` | List billing plans *(requires `paymentsAdminKey`)* |
187
+ | `getProductByAccountsId(accountsProductId)` | Resolve a payments product by its Accounts ID *(requires `paymentsAdminKey`)* |
188
+ | `getMrr(opts?)` | Fetch MRR analytics *(requires `paymentsAdminKey`)* |
189
+
190
+ ## License
191
+
192
+ MIT
@@ -0,0 +1,4 @@
1
+ export type { EntitlementResponse, LogoutResult, SessionResponse, TokenResponse, UserInToken, UserProfile, VerifyTokenResult, } from "./types";
2
+ export { effectiveAccessToken } from "./types";
3
+ export { AccountsNamespace } from "./namespace";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/accounts/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AccountsNamespace = exports.effectiveAccessToken = void 0;
4
+ var types_1 = require("./types");
5
+ Object.defineProperty(exports, "effectiveAccessToken", { enumerable: true, get: function () { return types_1.effectiveAccessToken; } });
6
+ var namespace_1 = require("./namespace");
7
+ Object.defineProperty(exports, "AccountsNamespace", { enumerable: true, get: function () { return namespace_1.AccountsNamespace; } });
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/accounts/index.ts"],"names":[],"mappings":";;;AAUA,iCAA+C;AAAtC,6GAAA,oBAAoB,OAAA;AAC7B,yCAAgD;AAAvC,8GAAA,iBAAiB,OAAA"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * AccountsNamespace — server-side OIDC + user operations.
3
+ *
4
+ * Handles the product-backend half of the OIDC flow:
5
+ * buildLoginUrl() → build the redirect URL to send the user to Accounts (synchronous)
6
+ * exchangeCode() → trade the auth code (from callback URL) for tokens
7
+ * refresh() → rotate tokens using a refresh token
8
+ * verifyToken() → validate an incoming access token (e.g. from a request header)
9
+ * logout() → revoke a session via refresh token (POST /oauth/logout)
10
+ * logoutAll() → revoke ALL sessions via access token (POST /auth/logout)
11
+ * revokeSession() → revoke a single session by ID (DELETE /auth/sessions/{id})
12
+ * getUser() → fetch the full user profile for the stored access token
13
+ * getEntitlements() → list entitlements for the authenticated user
14
+ * getSessions() → list active sessions for the authenticated user
15
+ *
16
+ * Session state:
17
+ * After a successful exchangeCode() or refresh() call, the client stores:
18
+ * _accessToken — attached automatically to getUser() / getEntitlements() / getSessions()
19
+ * _refreshToken — used automatically by refresh() and logout() if not passed explicitly
20
+ * _tokenExpiresAt — informational; not used for auto-refresh (caller's responsibility)
21
+ */
22
+ import { HttpTransport } from "../http";
23
+ import { EntitlementResponse, LogoutResult, SessionResponse, TokenResponse, UserProfile, VerifyTokenResult } from "./types";
24
+ export declare class AccountsNamespace {
25
+ private readonly _http;
26
+ private readonly _clientId;
27
+ private readonly _clientSecret;
28
+ private readonly _accountsUrl;
29
+ private _accessToken;
30
+ private _refreshToken;
31
+ private _tokenExpiresAt;
32
+ constructor(transport: HttpTransport, options: {
33
+ clientId: string;
34
+ clientSecret: string;
35
+ accountsUrl: string;
36
+ });
37
+ /**
38
+ * Build the URL to redirect the user's browser to the Accounts login page.
39
+ *
40
+ * This is step 2 of the OIDC flow — call this in your route handler and
41
+ * issue a 302 redirect to the returned URL. The Accounts frontend handles
42
+ * email/password login as well as Google and GitHub social login; the
43
+ * product backend never needs to call social-auth endpoints directly.
44
+ *
45
+ * @param redirectUri Must exactly match the redirect_uri registered for your product.
46
+ * @param state A cryptographically random string stored in the user's session
47
+ * to prevent CSRF attacks.
48
+ * @param opts.nextUrl Optional URL the Accounts frontend redirects to after login
49
+ * within its own domain (rarely needed).
50
+ * @returns The full login URL, e.g. `https://accounts.example.com/login?client_id=...`
51
+ */
52
+ buildLoginUrl(redirectUri: string, state: string, opts?: {
53
+ nextUrl?: string;
54
+ }): string;
55
+ /**
56
+ * Exchange the authorization code received on your callback URL for tokens.
57
+ *
58
+ * This is step 6 of the OIDC flow — called by your server after the
59
+ * Accounts frontend redirects the user back to your redirectUri with
60
+ * `?code=...&state=...` in the query string.
61
+ *
62
+ * @param code The raw 64-char hex auth code from the callback URL.
63
+ * @param redirectUri Must exactly match the redirect_uri registered for your product.
64
+ * @returns TokenResponse — contains tokens and an embedded UserInToken.
65
+ * Tokens are also stored internally for subsequent calls.
66
+ * @throws PpusshAuthError If the code is invalid, expired, or already used.
67
+ * @throws PpusshConsentRequired If the user has not consented to your product.
68
+ * @throws PpusshNetworkError If the request fails after all retries.
69
+ */
70
+ exchangeCode(code: string, redirectUri: string): Promise<TokenResponse>;
71
+ /**
72
+ * Rotate tokens using a refresh token.
73
+ *
74
+ * If refreshToken is omitted, the internally stored refresh token
75
+ * from the last exchangeCode() / refresh() call is used.
76
+ *
77
+ * @throws PpusshAuthError If the refresh token is invalid, expired, or replayed.
78
+ * Note: a replayed token causes ALL sessions to be revoked
79
+ * server-side — this is a security feature, not a bug.
80
+ */
81
+ refresh(refreshToken?: string): Promise<TokenResponse>;
82
+ /**
83
+ * Validate an access token your server received from an end-user request.
84
+ *
85
+ * Use this in your middleware / request handler to verify that the Bearer
86
+ * token a user sent to your product's API is valid and not expired.
87
+ *
88
+ * @param accessToken The raw JWT string from the `Authorization: Bearer ...` header.
89
+ * @returns VerifyTokenResult with valid, type, user_id, and email.
90
+ * @throws PpusshAuthError If the token is invalid, expired, or the account is deleted.
91
+ */
92
+ verifyToken(accessToken: string): Promise<VerifyTokenResult>;
93
+ /**
94
+ * Revoke a session and trigger front-channel logout to all connected products.
95
+ *
96
+ * Uses POST /oauth/logout with the refresh token — this is the standard
97
+ * per-session logout that also notifies downstream products via webhooks.
98
+ *
99
+ * If refreshToken is omitted, the internally stored refresh token is used.
100
+ * On success, stored tokens are cleared from the client instance.
101
+ *
102
+ * Logout is always safe to call — if the token is already invalid or the session
103
+ * doesn't exist, the Accounts server returns ok=true silently.
104
+ *
105
+ * @throws PpusshAuthError If client_id or client_secret are invalid.
106
+ */
107
+ logout(refreshToken?: string): Promise<LogoutResult>;
108
+ /**
109
+ * Revoke **all** sessions for the current user immediately.
110
+ *
111
+ * Uses POST /auth/logout with the access token (Bearer header).
112
+ * Unlike logout(), this does not require a refresh token and revokes every
113
+ * active session across all devices — useful for "sign out everywhere" UX.
114
+ *
115
+ * On success, stored tokens are cleared from the client instance.
116
+ *
117
+ * @param accessToken JWT access token. Optional if stored internally.
118
+ * @throws PpusshAuthError If the token is invalid or expired.
119
+ */
120
+ logoutAll(accessToken?: string): Promise<void>;
121
+ /**
122
+ * Revoke a specific session by its ID.
123
+ *
124
+ * Uses DELETE /auth/sessions/{sessionId} — the user can only revoke their
125
+ * own sessions. Useful for "sign out of this device" UX in a session
126
+ * management screen.
127
+ *
128
+ * @param sessionId The UUID of the session to revoke (from getSessions()).
129
+ * @param accessToken JWT access token. Optional if stored internally.
130
+ * @throws PpusshAuthError If the token is invalid or the session does not
131
+ * belong to the authenticated user.
132
+ */
133
+ revokeSession(sessionId: string, accessToken?: string): Promise<void>;
134
+ /**
135
+ * Fetch the full user profile for an access token.
136
+ *
137
+ * If accessToken is omitted, the internally stored token from the last
138
+ * exchangeCode() or refresh() is used.
139
+ *
140
+ * @throws PpusshAuthError If the token is invalid or expired.
141
+ */
142
+ getUser(accessToken?: string): Promise<UserProfile>;
143
+ /**
144
+ * List products the user has granted consent to (their entitlements).
145
+ *
146
+ * @param accessToken JWT access token. Optional if stored internally.
147
+ */
148
+ getEntitlements(accessToken?: string): Promise<EntitlementResponse[]>;
149
+ /**
150
+ * List all active sessions for the authenticated user.
151
+ *
152
+ * @param accessToken JWT access token. Optional if stored internally.
153
+ */
154
+ getSessions(accessToken?: string): Promise<SessionResponse[]>;
155
+ private _storeTokens;
156
+ private _clearTokens;
157
+ /** The currently stored access token, if any. */
158
+ get accessToken(): string | null;
159
+ /** The currently stored refresh token, if any. */
160
+ get refreshToken(): string | null;
161
+ /** UTC Date at which the stored access token expires, if known. */
162
+ get tokenExpiresAt(): Date | null;
163
+ }
164
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../src/accounts/namespace.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAEL,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,aAAa,EACb,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,eAAe,CAAqB;gBAG1C,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;IAU1E;;;;;;;;;;;;;;OAcG;IACH,aAAa,CACX,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1B,MAAM;IAcT;;;;;;;;;;;;;;OAcG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAe7E;;;;;;;;;OASG;IACG,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAuB5D;;;;;;;;;OASG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IASlE;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAoB1D;;;;;;;;;;;OAWG;IACG,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBpD;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe3E;;;;;;;OAOG;IACG,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgBzD;;;;OAIG;IACG,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAW3E;;;;OAIG;IACG,WAAW,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAanE,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,YAAY;IAMpB,iDAAiD;IACjD,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IAED,kDAAkD;IAClD,IAAI,YAAY,IAAI,MAAM,GAAG,IAAI,CAEhC;IAED,mEAAmE;IACnE,IAAI,cAAc,IAAI,IAAI,GAAG,IAAI,CAEhC;CACF"}
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ // ppussh/src/accounts/namespace.ts
3
+ /**
4
+ * AccountsNamespace — server-side OIDC + user operations.
5
+ *
6
+ * Handles the product-backend half of the OIDC flow:
7
+ * buildLoginUrl() → build the redirect URL to send the user to Accounts (synchronous)
8
+ * exchangeCode() → trade the auth code (from callback URL) for tokens
9
+ * refresh() → rotate tokens using a refresh token
10
+ * verifyToken() → validate an incoming access token (e.g. from a request header)
11
+ * logout() → revoke a session via refresh token (POST /oauth/logout)
12
+ * logoutAll() → revoke ALL sessions via access token (POST /auth/logout)
13
+ * revokeSession() → revoke a single session by ID (DELETE /auth/sessions/{id})
14
+ * getUser() → fetch the full user profile for the stored access token
15
+ * getEntitlements() → list entitlements for the authenticated user
16
+ * getSessions() → list active sessions for the authenticated user
17
+ *
18
+ * Session state:
19
+ * After a successful exchangeCode() or refresh() call, the client stores:
20
+ * _accessToken — attached automatically to getUser() / getEntitlements() / getSessions()
21
+ * _refreshToken — used automatically by refresh() and logout() if not passed explicitly
22
+ * _tokenExpiresAt — informational; not used for auto-refresh (caller's responsibility)
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.AccountsNamespace = void 0;
26
+ const types_1 = require("./types");
27
+ class AccountsNamespace {
28
+ constructor(transport, options) {
29
+ this._accessToken = null;
30
+ this._refreshToken = null;
31
+ this._tokenExpiresAt = null;
32
+ this._http = transport;
33
+ this._clientId = options.clientId;
34
+ this._clientSecret = options.clientSecret;
35
+ this._accountsUrl = options.accountsUrl;
36
+ }
37
+ // ── Login URL builder ──────────────────────────────────────────────────────
38
+ /**
39
+ * Build the URL to redirect the user's browser to the Accounts login page.
40
+ *
41
+ * This is step 2 of the OIDC flow — call this in your route handler and
42
+ * issue a 302 redirect to the returned URL. The Accounts frontend handles
43
+ * email/password login as well as Google and GitHub social login; the
44
+ * product backend never needs to call social-auth endpoints directly.
45
+ *
46
+ * @param redirectUri Must exactly match the redirect_uri registered for your product.
47
+ * @param state A cryptographically random string stored in the user's session
48
+ * to prevent CSRF attacks.
49
+ * @param opts.nextUrl Optional URL the Accounts frontend redirects to after login
50
+ * within its own domain (rarely needed).
51
+ * @returns The full login URL, e.g. `https://accounts.example.com/login?client_id=...`
52
+ */
53
+ buildLoginUrl(redirectUri, state, opts) {
54
+ const params = new URLSearchParams({
55
+ client_id: this._clientId,
56
+ redirect_uri: redirectUri,
57
+ state,
58
+ });
59
+ if (opts?.nextUrl) {
60
+ params.set("next", opts.nextUrl);
61
+ }
62
+ return `${this._accountsUrl}/login?${params.toString()}`;
63
+ }
64
+ // ── OIDC token exchange ────────────────────────────────────────────────────
65
+ /**
66
+ * Exchange the authorization code received on your callback URL for tokens.
67
+ *
68
+ * This is step 6 of the OIDC flow — called by your server after the
69
+ * Accounts frontend redirects the user back to your redirectUri with
70
+ * `?code=...&state=...` in the query string.
71
+ *
72
+ * @param code The raw 64-char hex auth code from the callback URL.
73
+ * @param redirectUri Must exactly match the redirect_uri registered for your product.
74
+ * @returns TokenResponse — contains tokens and an embedded UserInToken.
75
+ * Tokens are also stored internally for subsequent calls.
76
+ * @throws PpusshAuthError If the code is invalid, expired, or already used.
77
+ * @throws PpusshConsentRequired If the user has not consented to your product.
78
+ * @throws PpusshNetworkError If the request fails after all retries.
79
+ */
80
+ async exchangeCode(code, redirectUri) {
81
+ const response = await this._http.request("POST", "/oauth/token", {
82
+ form: {
83
+ grant_type: "authorization_code",
84
+ code,
85
+ client_id: this._clientId,
86
+ client_secret: this._clientSecret,
87
+ redirect_uri: redirectUri,
88
+ },
89
+ });
90
+ const token = response.data;
91
+ this._storeTokens(token);
92
+ return token;
93
+ }
94
+ /**
95
+ * Rotate tokens using a refresh token.
96
+ *
97
+ * If refreshToken is omitted, the internally stored refresh token
98
+ * from the last exchangeCode() / refresh() call is used.
99
+ *
100
+ * @throws PpusshAuthError If the refresh token is invalid, expired, or replayed.
101
+ * Note: a replayed token causes ALL sessions to be revoked
102
+ * server-side — this is a security feature, not a bug.
103
+ */
104
+ async refresh(refreshToken) {
105
+ const tokenToUse = refreshToken ?? this._refreshToken;
106
+ if (!tokenToUse) {
107
+ throw new Error("No refreshToken provided and none stored. " +
108
+ "Call exchangeCode() first or pass refreshToken explicitly.");
109
+ }
110
+ const response = await this._http.request("POST", "/oauth/token", {
111
+ form: {
112
+ grant_type: "refresh_token",
113
+ refresh_token: tokenToUse,
114
+ client_id: this._clientId,
115
+ client_secret: this._clientSecret,
116
+ },
117
+ });
118
+ const token = response.data;
119
+ this._storeTokens(token);
120
+ return token;
121
+ }
122
+ // ── Token verification ─────────────────────────────────────────────────────
123
+ /**
124
+ * Validate an access token your server received from an end-user request.
125
+ *
126
+ * Use this in your middleware / request handler to verify that the Bearer
127
+ * token a user sent to your product's API is valid and not expired.
128
+ *
129
+ * @param accessToken The raw JWT string from the `Authorization: Bearer ...` header.
130
+ * @returns VerifyTokenResult with valid, type, user_id, and email.
131
+ * @throws PpusshAuthError If the token is invalid, expired, or the account is deleted.
132
+ */
133
+ async verifyToken(accessToken) {
134
+ const response = await this._http.request("GET", "/auth/verify-token", {
135
+ headers: { Authorization: `Bearer ${accessToken}` },
136
+ });
137
+ return response.data;
138
+ }
139
+ // ── Logout ─────────────────────────────────────────────────────────────────
140
+ /**
141
+ * Revoke a session and trigger front-channel logout to all connected products.
142
+ *
143
+ * Uses POST /oauth/logout with the refresh token — this is the standard
144
+ * per-session logout that also notifies downstream products via webhooks.
145
+ *
146
+ * If refreshToken is omitted, the internally stored refresh token is used.
147
+ * On success, stored tokens are cleared from the client instance.
148
+ *
149
+ * Logout is always safe to call — if the token is already invalid or the session
150
+ * doesn't exist, the Accounts server returns ok=true silently.
151
+ *
152
+ * @throws PpusshAuthError If client_id or client_secret are invalid.
153
+ */
154
+ async logout(refreshToken) {
155
+ const tokenToUse = refreshToken ?? this._refreshToken;
156
+ if (!tokenToUse) {
157
+ throw new Error("No refreshToken provided and none stored. " +
158
+ "Call exchangeCode() first or pass refreshToken explicitly.");
159
+ }
160
+ const response = await this._http.request("POST", "/oauth/logout", {
161
+ json: {
162
+ refresh_token: tokenToUse,
163
+ client_id: this._clientId,
164
+ client_secret: this._clientSecret,
165
+ },
166
+ });
167
+ const result = response.data;
168
+ this._clearTokens();
169
+ return result;
170
+ }
171
+ /**
172
+ * Revoke **all** sessions for the current user immediately.
173
+ *
174
+ * Uses POST /auth/logout with the access token (Bearer header).
175
+ * Unlike logout(), this does not require a refresh token and revokes every
176
+ * active session across all devices — useful for "sign out everywhere" UX.
177
+ *
178
+ * On success, stored tokens are cleared from the client instance.
179
+ *
180
+ * @param accessToken JWT access token. Optional if stored internally.
181
+ * @throws PpusshAuthError If the token is invalid or expired.
182
+ */
183
+ async logoutAll(accessToken) {
184
+ const tokenToUse = accessToken ?? this._accessToken;
185
+ if (!tokenToUse) {
186
+ throw new Error("No accessToken provided and none stored. " +
187
+ "Call exchangeCode() first or pass accessToken explicitly.");
188
+ }
189
+ await this._http.request("POST", "/auth/logout", {
190
+ headers: { Authorization: `Bearer ${tokenToUse}` },
191
+ });
192
+ this._clearTokens();
193
+ }
194
+ // ── Session management ─────────────────────────────────────────────────────
195
+ /**
196
+ * Revoke a specific session by its ID.
197
+ *
198
+ * Uses DELETE /auth/sessions/{sessionId} — the user can only revoke their
199
+ * own sessions. Useful for "sign out of this device" UX in a session
200
+ * management screen.
201
+ *
202
+ * @param sessionId The UUID of the session to revoke (from getSessions()).
203
+ * @param accessToken JWT access token. Optional if stored internally.
204
+ * @throws PpusshAuthError If the token is invalid or the session does not
205
+ * belong to the authenticated user.
206
+ */
207
+ async revokeSession(sessionId, accessToken) {
208
+ const tokenToUse = accessToken ?? this._accessToken;
209
+ if (!tokenToUse) {
210
+ throw new Error("No accessToken provided and none stored. " +
211
+ "Call exchangeCode() first or pass accessToken explicitly.");
212
+ }
213
+ await this._http.request("DELETE", `/auth/sessions/${sessionId}`, {
214
+ headers: { Authorization: `Bearer ${tokenToUse}` },
215
+ });
216
+ }
217
+ // ── User profile ───────────────────────────────────────────────────────────
218
+ /**
219
+ * Fetch the full user profile for an access token.
220
+ *
221
+ * If accessToken is omitted, the internally stored token from the last
222
+ * exchangeCode() or refresh() is used.
223
+ *
224
+ * @throws PpusshAuthError If the token is invalid or expired.
225
+ */
226
+ async getUser(accessToken) {
227
+ const tokenToUse = accessToken ?? this._accessToken;
228
+ if (!tokenToUse) {
229
+ throw new Error("No accessToken provided and none stored. " +
230
+ "Call exchangeCode() first or pass accessToken explicitly.");
231
+ }
232
+ const response = await this._http.request("GET", "/users/me", {
233
+ headers: { Authorization: `Bearer ${tokenToUse}` },
234
+ });
235
+ return response.data;
236
+ }
237
+ // ── Entitlements & sessions ────────────────────────────────────────────────
238
+ /**
239
+ * List products the user has granted consent to (their entitlements).
240
+ *
241
+ * @param accessToken JWT access token. Optional if stored internally.
242
+ */
243
+ async getEntitlements(accessToken) {
244
+ const tokenToUse = accessToken ?? this._accessToken;
245
+ if (!tokenToUse) {
246
+ throw new Error("No accessToken provided and none stored.");
247
+ }
248
+ const response = await this._http.request("GET", "/users/me/entitlements", {
249
+ headers: { Authorization: `Bearer ${tokenToUse}` },
250
+ });
251
+ return response.data;
252
+ }
253
+ /**
254
+ * List all active sessions for the authenticated user.
255
+ *
256
+ * @param accessToken JWT access token. Optional if stored internally.
257
+ */
258
+ async getSessions(accessToken) {
259
+ const tokenToUse = accessToken ?? this._accessToken;
260
+ if (!tokenToUse) {
261
+ throw new Error("No accessToken provided and none stored.");
262
+ }
263
+ const response = await this._http.request("GET", "/users/me/sessions", {
264
+ headers: { Authorization: `Bearer ${tokenToUse}` },
265
+ });
266
+ return response.data;
267
+ }
268
+ // ── Internal token management ──────────────────────────────────────────────
269
+ _storeTokens(token) {
270
+ this._accessToken = (0, types_1.effectiveAccessToken)(token);
271
+ this._refreshToken = token.refresh_token;
272
+ this._tokenExpiresAt = new Date(Date.now() + token.expires_in * 1000);
273
+ }
274
+ _clearTokens() {
275
+ this._accessToken = null;
276
+ this._refreshToken = null;
277
+ this._tokenExpiresAt = null;
278
+ }
279
+ /** The currently stored access token, if any. */
280
+ get accessToken() {
281
+ return this._accessToken;
282
+ }
283
+ /** The currently stored refresh token, if any. */
284
+ get refreshToken() {
285
+ return this._refreshToken;
286
+ }
287
+ /** UTC Date at which the stored access token expires, if known. */
288
+ get tokenExpiresAt() {
289
+ return this._tokenExpiresAt;
290
+ }
291
+ }
292
+ exports.AccountsNamespace = AccountsNamespace;
293
+ //# sourceMappingURL=namespace.js.map