@oneaccount/express 0.1.2 → 0.2.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
@@ -90,6 +90,67 @@ When you call `oa.mountRoutes(app, '/api/connect')`, the following routes are av
90
90
  | POST | `/api/connect/payment` | Create marketplace payment |
91
91
  | POST | `/api/connect/refund` | Refund a payment |
92
92
 
93
+ ## Buyer Authentication
94
+
95
+ For marketplace apps where sellers have their own customers (buyers), the SDK provides buyer authentication:
96
+
97
+ ```typescript
98
+ import type { BuyerAuthRequest } from '@oneaccount/express';
99
+
100
+ // Mount buyer auth routes
101
+ oa.mountBuyerRoutes(app, '/api/buyer');
102
+
103
+ // Add buyer middleware to routes that need buyer auth
104
+ app.use('/api/customer', oa.buyerMiddleware);
105
+
106
+ // Protected buyer route - use BuyerAuthRequest for typing
107
+ app.get('/api/customer/orders', oa.requireBuyerAuth, (req: BuyerAuthRequest, res) => {
108
+ const buyer = req.buyer!; // Non-null after requireBuyerAuth
109
+ res.json({ buyerId: buyer.buyerId, sellerId: buyer.sellerId });
110
+ });
111
+
112
+ // Restrict to specific seller's buyers
113
+ app.get('/api/customer/classes', oa.requireBuyerForSeller('seller-uuid'), (req: BuyerAuthRequest, res) => {
114
+ res.json({ message: 'Welcome!' });
115
+ });
116
+ ```
117
+
118
+ ### Buyer Auth Routes
119
+
120
+ When you call `oa.mountBuyerRoutes(app, '/api/buyer')`:
121
+
122
+ | Method | Path | Description |
123
+ |--------|------|-------------|
124
+ | POST | `/api/buyer/magic/request` | Request magic login link (email or SMS) |
125
+ | GET | `/api/buyer/magic/verify` | Verify magic token and get JWT |
126
+ | GET | `/api/buyer/profile` | Get buyer profile (requires auth) |
127
+
128
+ ### Magic Link Request Body
129
+
130
+ ```json
131
+ {
132
+ "email": "customer@example.com",
133
+ "sellerId": "seller-uuid",
134
+ "channel": "email"
135
+ }
136
+ ```
137
+
138
+ Or for SMS:
139
+
140
+ ```json
141
+ {
142
+ "phone": "+15551234567",
143
+ "sellerId": "seller-uuid",
144
+ "channel": "sms"
145
+ }
146
+ ```
147
+
148
+ ### Buyer Middleware
149
+
150
+ - `oa.buyerMiddleware` - Parses buyer JWT from `Authorization: Bearer <token>` header
151
+ - `oa.requireBuyerAuth` - Returns 401 if no authenticated buyer
152
+ - `oa.requireBuyerForSeller(sellerId)` - Returns 403 if buyer doesn't belong to seller
153
+
93
154
  ## TypeScript
94
155
 
95
156
  The SDK is fully typed. Import types as needed:
@@ -98,7 +159,9 @@ The SDK is fully typed. Import types as needed:
98
159
  import type {
99
160
  OneAccountRequest,
100
161
  OneAccountUser,
101
- Entitlements
162
+ Entitlements,
163
+ BuyerAuthRequest,
164
+ BuyerUser,
102
165
  } from '@oneaccount/express';
103
166
  ```
104
167
 
@@ -1,4 +1,4 @@
1
- import type { OneAccountConfig, StripeConnectStatus, StripeTransaction, StripeBalance } from "../types";
1
+ import type { OneAccountConfig, StripeConnectStatus, StripeTransaction, StripeBalance, BuyerMagicRequestData, BuyerMagicVerifyResponse, BuyerProfileResponse } from "../types";
2
2
  export declare class AccountProClient {
3
3
  private baseUrl;
4
4
  private apiKey;
@@ -40,4 +40,10 @@ export declare class AccountProClient {
40
40
  refundId: string;
41
41
  status: string;
42
42
  }>;
43
+ requestBuyerMagicLink(data: BuyerMagicRequestData): Promise<{
44
+ success: boolean;
45
+ message: string;
46
+ }>;
47
+ verifyBuyerMagicToken(token: string): Promise<BuyerMagicVerifyResponse>;
48
+ getBuyerProfile(buyerToken: string): Promise<BuyerProfileResponse>;
43
49
  }
@@ -82,5 +82,18 @@ class AccountProClient {
82
82
  body: JSON.stringify(data),
83
83
  });
84
84
  }
85
+ // Buyer Auth Methods
86
+ async requestBuyerMagicLink(data) {
87
+ return this.request("/buyer/magic/request", {
88
+ method: "POST",
89
+ body: JSON.stringify(data),
90
+ });
91
+ }
92
+ async verifyBuyerMagicToken(token) {
93
+ return this.request(`/buyer/magic/verify?token=${encodeURIComponent(token)}`);
94
+ }
95
+ async getBuyerProfile(buyerToken) {
96
+ return this.request("/buyer/profile", { token: buyerToken });
97
+ }
85
98
  }
86
99
  exports.AccountProClient = AccountProClient;
package/dist/index.d.ts CHANGED
@@ -1,17 +1,24 @@
1
1
  import type { Express } from "express";
2
2
  import { createAuthMiddleware, requireAuth, requireEntitlement, requireSuperAdmin } from "./middleware/auth";
3
+ import { createBuyerAuthMiddleware, requireBuyerAuth, requireBuyerForSeller } from "./middleware/buyerAuth";
3
4
  import { AccountProClient } from "./client/accountPro";
4
5
  import type { OneAccountConfig } from "./types";
5
6
  export interface OneAccountSDK {
6
7
  middleware: ReturnType<typeof createAuthMiddleware>;
8
+ buyerMiddleware: ReturnType<typeof createBuyerAuthMiddleware>;
7
9
  requireAuth: typeof requireAuth;
8
10
  requireEntitlement: typeof requireEntitlement;
9
11
  requireSuperAdmin: typeof requireSuperAdmin;
12
+ requireBuyerAuth: typeof requireBuyerAuth;
13
+ requireBuyerForSeller: typeof requireBuyerForSeller;
10
14
  client: AccountProClient;
11
15
  mountRoutes: (app: Express, basePath?: string) => void;
16
+ mountBuyerRoutes: (app: Express, basePath?: string) => void;
12
17
  }
13
18
  export declare function oneAccount(config: OneAccountConfig): OneAccountSDK;
14
19
  export { createAuthMiddleware, requireAuth, requireEntitlement, requireSuperAdmin, } from "./middleware/auth";
20
+ export { createBuyerAuthMiddleware, requireBuyerAuth, requireBuyerForSeller, } from "./middleware/buyerAuth";
15
21
  export { createStripeConnectRoutes } from "./routes/stripeConnect";
22
+ export { createBuyerAuthRoutes } from "./routes/buyerAuth";
16
23
  export { AccountProClient } from "./client/accountPro";
17
- export type { OneAccountConfig, OneAccountRequest, OneAccountUser, Entitlements, } from "./types";
24
+ export type { OneAccountConfig, OneAccountRequest, OneAccountUser, Entitlements, BuyerAuthRequest, BuyerUser, BuyerMagicRequestData, BuyerMagicVerifyResponse, BuyerProfileResponse, } from "./types";
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AccountProClient = exports.createStripeConnectRoutes = exports.requireSuperAdmin = exports.requireEntitlement = exports.requireAuth = exports.createAuthMiddleware = void 0;
3
+ exports.AccountProClient = exports.createBuyerAuthRoutes = exports.createStripeConnectRoutes = exports.requireBuyerForSeller = exports.requireBuyerAuth = exports.createBuyerAuthMiddleware = exports.requireSuperAdmin = exports.requireEntitlement = exports.requireAuth = exports.createAuthMiddleware = void 0;
4
4
  exports.oneAccount = oneAccount;
5
5
  const auth_1 = require("./middleware/auth");
6
+ const buyerAuth_1 = require("./middleware/buyerAuth");
6
7
  const stripeConnect_1 = require("./routes/stripeConnect");
8
+ const buyerAuth_2 = require("./routes/buyerAuth");
7
9
  const accountPro_1 = require("./client/accountPro");
8
10
  function oneAccount(config) {
9
11
  if (!config.apiKey) {
@@ -14,16 +16,23 @@ function oneAccount(config) {
14
16
  ...config,
15
17
  };
16
18
  const authMiddleware = (0, auth_1.createAuthMiddleware)(resolvedConfig);
19
+ const buyerAuthMiddleware = (0, buyerAuth_1.createBuyerAuthMiddleware)(resolvedConfig);
17
20
  const client = new accountPro_1.AccountProClient(resolvedConfig);
18
21
  return {
19
22
  middleware: authMiddleware,
23
+ buyerMiddleware: buyerAuthMiddleware,
20
24
  requireAuth: auth_1.requireAuth,
21
25
  requireEntitlement: auth_1.requireEntitlement,
22
26
  requireSuperAdmin: auth_1.requireSuperAdmin,
27
+ requireBuyerAuth: buyerAuth_1.requireBuyerAuth,
28
+ requireBuyerForSeller: buyerAuth_1.requireBuyerForSeller,
23
29
  client,
24
30
  mountRoutes: (app, basePath = "/api/connect") => {
25
31
  app.use(basePath, (0, stripeConnect_1.createStripeConnectRoutes)(resolvedConfig));
26
32
  },
33
+ mountBuyerRoutes: (app, basePath = "/api/buyer") => {
34
+ app.use(basePath, (0, buyerAuth_2.createBuyerAuthRoutes)(resolvedConfig));
35
+ },
27
36
  };
28
37
  }
29
38
  var auth_2 = require("./middleware/auth");
@@ -31,7 +40,13 @@ Object.defineProperty(exports, "createAuthMiddleware", { enumerable: true, get:
31
40
  Object.defineProperty(exports, "requireAuth", { enumerable: true, get: function () { return auth_2.requireAuth; } });
32
41
  Object.defineProperty(exports, "requireEntitlement", { enumerable: true, get: function () { return auth_2.requireEntitlement; } });
33
42
  Object.defineProperty(exports, "requireSuperAdmin", { enumerable: true, get: function () { return auth_2.requireSuperAdmin; } });
43
+ var buyerAuth_3 = require("./middleware/buyerAuth");
44
+ Object.defineProperty(exports, "createBuyerAuthMiddleware", { enumerable: true, get: function () { return buyerAuth_3.createBuyerAuthMiddleware; } });
45
+ Object.defineProperty(exports, "requireBuyerAuth", { enumerable: true, get: function () { return buyerAuth_3.requireBuyerAuth; } });
46
+ Object.defineProperty(exports, "requireBuyerForSeller", { enumerable: true, get: function () { return buyerAuth_3.requireBuyerForSeller; } });
34
47
  var stripeConnect_2 = require("./routes/stripeConnect");
35
48
  Object.defineProperty(exports, "createStripeConnectRoutes", { enumerable: true, get: function () { return stripeConnect_2.createStripeConnectRoutes; } });
49
+ var buyerAuth_4 = require("./routes/buyerAuth");
50
+ Object.defineProperty(exports, "createBuyerAuthRoutes", { enumerable: true, get: function () { return buyerAuth_4.createBuyerAuthRoutes; } });
36
51
  var accountPro_2 = require("./client/accountPro");
37
52
  Object.defineProperty(exports, "AccountProClient", { enumerable: true, get: function () { return accountPro_2.AccountProClient; } });
@@ -0,0 +1,5 @@
1
+ import type { Response, NextFunction } from "express";
2
+ import type { BuyerAuthRequest, OneAccountConfig } from "../types";
3
+ export declare function createBuyerAuthMiddleware(config: OneAccountConfig): (req: BuyerAuthRequest, _res: Response, next: NextFunction) => Promise<void>;
4
+ export declare function requireBuyerAuth(req: BuyerAuthRequest, res: Response, next: NextFunction): Response<any, Record<string, any>> | undefined;
5
+ export declare function requireBuyerForSeller(sellerId: string): (req: BuyerAuthRequest, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createBuyerAuthMiddleware = createBuyerAuthMiddleware;
7
+ exports.requireBuyerAuth = requireBuyerAuth;
8
+ exports.requireBuyerForSeller = requireBuyerForSeller;
9
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
10
+ let cachedJWKS = null;
11
+ let jwksCacheExpiry = 0;
12
+ async function fetchJWKS(jwksUrl) {
13
+ const now = Date.now();
14
+ if (cachedJWKS && now < jwksCacheExpiry) {
15
+ return cachedJWKS;
16
+ }
17
+ const response = await fetch(jwksUrl);
18
+ if (!response.ok) {
19
+ throw new Error(`Failed to fetch JWKS: ${response.status}`);
20
+ }
21
+ cachedJWKS = await response.json();
22
+ jwksCacheExpiry = now + 3600000;
23
+ return cachedJWKS;
24
+ }
25
+ function rsaPublicKeyFromJWK(jwk) {
26
+ const n = Buffer.from(jwk.n, "base64url");
27
+ const e = Buffer.from(jwk.e, "base64url");
28
+ const nLen = n.length;
29
+ const eLen = e.length;
30
+ const nLenBytes = nLen < 128
31
+ ? [nLen]
32
+ : nLen < 256
33
+ ? [0x81, nLen]
34
+ : [0x82, (nLen >> 8) & 0xff, nLen & 0xff];
35
+ const eLenBytes = eLen < 128
36
+ ? [eLen]
37
+ : eLen < 256
38
+ ? [0x81, eLen]
39
+ : [0x82, (eLen >> 8) & 0xff, eLen & 0xff];
40
+ const nSequence = [0x02, ...nLenBytes, ...n];
41
+ const eSequence = [0x02, ...eLenBytes, ...e];
42
+ const innerSequence = [...nSequence, ...eSequence];
43
+ const innerLen = innerSequence.length;
44
+ const innerLenBytes = innerLen < 128
45
+ ? [innerLen]
46
+ : innerLen < 256
47
+ ? [0x81, innerLen]
48
+ : [0x82, (innerLen >> 8) & 0xff, innerLen & 0xff];
49
+ const bitString = [0x30, ...innerLenBytes, ...innerSequence];
50
+ const bitStringWithHeader = [
51
+ 0x03,
52
+ bitString.length + 1,
53
+ 0x00,
54
+ ...bitString,
55
+ ];
56
+ const rsaOID = [
57
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
58
+ 0x01, 0x05, 0x00,
59
+ ];
60
+ const fullSequence = [...rsaOID, ...bitStringWithHeader];
61
+ const fullLen = fullSequence.length;
62
+ const fullLenBytes = fullLen < 128
63
+ ? [fullLen]
64
+ : fullLen < 256
65
+ ? [0x81, fullLen]
66
+ : [0x82, (fullLen >> 8) & 0xff, fullLen & 0xff];
67
+ const der = Buffer.from([0x30, ...fullLenBytes, ...fullSequence]);
68
+ const pem = `-----BEGIN PUBLIC KEY-----\n${der.toString("base64").match(/.{1,64}/g)?.join("\n")}\n-----END PUBLIC KEY-----`;
69
+ return pem;
70
+ }
71
+ function createBuyerAuthMiddleware(config) {
72
+ const jwksUrl = config.jwksUrl ||
73
+ `${config.accountProUrl || "https://myaccount.one"}/.well-known/jwks.json`;
74
+ return async function buyerAuthMiddleware(req, _res, next) {
75
+ req.buyer = null;
76
+ const authHeader = req.headers.authorization;
77
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : null;
78
+ if (!token) {
79
+ return next();
80
+ }
81
+ try {
82
+ const decoded = jsonwebtoken_1.default.decode(token, { complete: true });
83
+ if (!decoded) {
84
+ return next();
85
+ }
86
+ const jwks = await fetchJWKS(jwksUrl);
87
+ const key = decoded.header.kid
88
+ ? jwks.keys.find((k) => k.kid === decoded.header.kid)
89
+ : jwks.keys[0];
90
+ if (!key) {
91
+ if (config.debug) {
92
+ console.error("[OneAccount] No matching key found in JWKS for buyer token");
93
+ }
94
+ return next();
95
+ }
96
+ const publicKey = rsaPublicKeyFromJWK(key);
97
+ const payload = jsonwebtoken_1.default.verify(token, publicKey, {
98
+ algorithms: ["RS256"],
99
+ });
100
+ if (payload.buyerId && payload.sellerId) {
101
+ req.buyer = {
102
+ buyerId: payload.buyerId,
103
+ sellerId: payload.sellerId,
104
+ email: payload.email,
105
+ name: payload.name,
106
+ phone: payload.phone,
107
+ };
108
+ }
109
+ }
110
+ catch (err) {
111
+ if (config.debug) {
112
+ console.error("[OneAccount] Buyer JWT verification failed:", err);
113
+ }
114
+ }
115
+ next();
116
+ };
117
+ }
118
+ function requireBuyerAuth(req, res, next) {
119
+ if (!req.buyer) {
120
+ return res.status(401).json({ error: "Buyer authentication required" });
121
+ }
122
+ next();
123
+ }
124
+ function requireBuyerForSeller(sellerId) {
125
+ return function (req, res, next) {
126
+ if (!req.buyer) {
127
+ return res.status(401).json({ error: "Buyer authentication required" });
128
+ }
129
+ if (req.buyer.sellerId !== sellerId) {
130
+ return res.status(403).json({ error: "Access denied for this seller" });
131
+ }
132
+ next();
133
+ };
134
+ }
@@ -0,0 +1,3 @@
1
+ import type { Router } from "express";
2
+ import type { OneAccountConfig } from "../types";
3
+ export declare function createBuyerAuthRoutes(config: OneAccountConfig): Router;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBuyerAuthRoutes = createBuyerAuthRoutes;
4
+ const express_1 = require("express");
5
+ const accountPro_1 = require("../client/accountPro");
6
+ function createBuyerAuthRoutes(config) {
7
+ const router = (0, express_1.Router)();
8
+ const client = new accountPro_1.AccountProClient(config);
9
+ router.post("/magic/request", async (req, res) => {
10
+ try {
11
+ const { email, phone, sellerId, channel: rawChannel } = req.body;
12
+ if (!sellerId) {
13
+ return res.status(400).json({ error: "sellerId is required" });
14
+ }
15
+ // Default to email if channel not specified
16
+ const channel = rawChannel || "email";
17
+ if (channel === "email" && !email) {
18
+ return res.status(400).json({ error: "email is required for email channel" });
19
+ }
20
+ if (channel === "sms" && !phone) {
21
+ return res.status(400).json({ error: "phone is required for SMS channel" });
22
+ }
23
+ const result = await client.requestBuyerMagicLink({
24
+ email,
25
+ phone,
26
+ sellerId,
27
+ channel,
28
+ });
29
+ res.json(result);
30
+ }
31
+ catch (error) {
32
+ const message = error instanceof Error ? error.message : "Failed to request magic link";
33
+ res.status(400).json({ error: message });
34
+ }
35
+ });
36
+ router.get("/magic/verify", async (req, res) => {
37
+ try {
38
+ const token = req.query.token;
39
+ if (!token) {
40
+ return res.status(400).json({ error: "token is required" });
41
+ }
42
+ const result = await client.verifyBuyerMagicToken(token);
43
+ if (result.success && result.token) {
44
+ res.json({
45
+ success: true,
46
+ token: result.token,
47
+ buyer: result.buyer,
48
+ });
49
+ }
50
+ else {
51
+ res.status(401).json({
52
+ success: false,
53
+ error: result.error || "Invalid or expired token",
54
+ });
55
+ }
56
+ }
57
+ catch (error) {
58
+ const message = error instanceof Error ? error.message : "Failed to verify token";
59
+ res.status(400).json({ error: message });
60
+ }
61
+ });
62
+ router.get("/profile", async (req, res) => {
63
+ try {
64
+ const authHeader = req.headers.authorization;
65
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
66
+ if (!token) {
67
+ return res.status(401).json({ error: "Authorization required" });
68
+ }
69
+ const profile = await client.getBuyerProfile(token);
70
+ res.json(profile);
71
+ }
72
+ catch (error) {
73
+ const message = error instanceof Error ? error.message : "Failed to get profile";
74
+ res.status(400).json({ error: message });
75
+ }
76
+ });
77
+ return router;
78
+ }
@@ -60,3 +60,32 @@ export interface StripeBalance {
60
60
  currency: string;
61
61
  }[];
62
62
  }
63
+ export interface BuyerUser {
64
+ buyerId: string;
65
+ sellerId: string;
66
+ email: string;
67
+ name?: string;
68
+ phone?: string;
69
+ }
70
+ export interface BuyerAuthRequest extends Request {
71
+ buyer?: BuyerUser | null;
72
+ }
73
+ export interface BuyerMagicRequestData {
74
+ email?: string;
75
+ phone?: string;
76
+ sellerId: string;
77
+ channel?: "email" | "sms";
78
+ }
79
+ export interface BuyerMagicVerifyResponse {
80
+ success: boolean;
81
+ token?: string;
82
+ buyer?: BuyerUser;
83
+ error?: string;
84
+ }
85
+ export interface BuyerProfileResponse {
86
+ id: string;
87
+ email: string;
88
+ name?: string;
89
+ phone?: string;
90
+ sellerId: string;
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneaccount/express",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "OneAccount SDK for Express.js - Authentication, entitlements, and Stripe Connect",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",