@oneaccount/express 0.1.1 → 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 +64 -1
- package/dist/client/accountPro.d.ts +7 -1
- package/dist/client/accountPro.js +13 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +16 -1
- package/dist/middleware/auth.js +21 -0
- package/dist/middleware/buyerAuth.d.ts +5 -0
- package/dist/middleware/buyerAuth.js +134 -0
- package/dist/routes/buyerAuth.d.ts +3 -0
- package/dist/routes/buyerAuth.js +78 -0
- package/dist/types/index.d.ts +37 -0
- package/package.json +1 -1
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; } });
|
package/dist/middleware/auth.js
CHANGED
|
@@ -85,8 +85,29 @@ function createAuthMiddleware(config) {
|
|
|
85
85
|
const jwksUrl = config.jwksUrl ||
|
|
86
86
|
`${config.accountProUrl || "https://myaccount.one"}/.well-known/jwks.json`;
|
|
87
87
|
const cookieName = config.cookieName || "auth_token";
|
|
88
|
+
const autoSetCookie = config.autoSetCookie !== false; // Default to true
|
|
88
89
|
return async function authMiddleware(req, res, next) {
|
|
89
90
|
req.oneAccount = { user: null };
|
|
91
|
+
// Auto-set cookie from ?token= query parameter (SSO redirect handling)
|
|
92
|
+
if (autoSetCookie && req.query?.token && typeof req.query.token === 'string') {
|
|
93
|
+
const tokenFromUrl = req.query.token;
|
|
94
|
+
// Set the cookie
|
|
95
|
+
res.cookie(cookieName, tokenFromUrl, {
|
|
96
|
+
httpOnly: true,
|
|
97
|
+
secure: process.env.NODE_ENV === 'production',
|
|
98
|
+
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
99
|
+
sameSite: 'lax',
|
|
100
|
+
path: '/',
|
|
101
|
+
});
|
|
102
|
+
// Build redirect URL without the token parameter
|
|
103
|
+
const url = new URL(req.originalUrl || req.url, `${req.protocol}://${req.get('host')}`);
|
|
104
|
+
url.searchParams.delete('token');
|
|
105
|
+
const redirectPath = url.pathname + url.search;
|
|
106
|
+
if (config.debug) {
|
|
107
|
+
console.log(`[OneAccount] Auto-set cookie from URL token, redirecting to ${redirectPath}`);
|
|
108
|
+
}
|
|
109
|
+
return res.redirect(redirectPath);
|
|
110
|
+
}
|
|
90
111
|
let token = null;
|
|
91
112
|
const authHeader = req.headers.authorization;
|
|
92
113
|
if (authHeader?.startsWith("Bearer ")) {
|
|
@@ -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,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
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,6 +25,14 @@ export interface OneAccountConfig {
|
|
|
25
25
|
cacheMaxAge?: number;
|
|
26
26
|
debug?: boolean;
|
|
27
27
|
cookieName?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Automatically set auth cookie from ?token= URL parameter (SSO redirect handling).
|
|
30
|
+
* When enabled (default: true), the middleware will:
|
|
31
|
+
* 1. Detect ?token= in the URL
|
|
32
|
+
* 2. Set it as an HttpOnly cookie
|
|
33
|
+
* 3. Redirect to the same URL without the token parameter
|
|
34
|
+
*/
|
|
35
|
+
autoSetCookie?: boolean;
|
|
28
36
|
}
|
|
29
37
|
export interface StripeConnectStatus {
|
|
30
38
|
hasAccount: boolean;
|
|
@@ -52,3 +60,32 @@ export interface StripeBalance {
|
|
|
52
60
|
currency: string;
|
|
53
61
|
}[];
|
|
54
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
|
+
}
|