archetecture security fix
This commit is contained in:
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
export declare function generateCompanyApiKey(): {
|
||||
rawKey: string;
|
||||
prefix: string;
|
||||
keyHash: string;
|
||||
};
|
||||
export declare function getApiKeyPrefix(rawKey: string): string | null;
|
||||
export declare function hashApiKey(rawKey: string): string;
|
||||
export declare function timingSafeEqualHex(a: string, b: string): boolean;
|
||||
//# sourceMappingURL=apiKeys.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"apiKeys.d.ts","sourceRoot":"","sources":["../../src/security/apiKeys.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB;;;;EAKpC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,iBAI7C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,UAExC;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,WAKtD"}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.generateCompanyApiKey = generateCompanyApiKey;
|
||||
exports.getApiKeyPrefix = getApiKeyPrefix;
|
||||
exports.hashApiKey = hashApiKey;
|
||||
exports.timingSafeEqualHex = timingSafeEqualHex;
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
function generateCompanyApiKey() {
|
||||
const secret = crypto_1.default.randomBytes(32).toString('base64url');
|
||||
const prefix = `rdg_${crypto_1.default.randomBytes(6).toString('hex')}`;
|
||||
const rawKey = `${prefix}_${secret}`;
|
||||
return { rawKey, prefix, keyHash: hashApiKey(rawKey) };
|
||||
}
|
||||
function getApiKeyPrefix(rawKey) {
|
||||
const parts = rawKey.split('_');
|
||||
if (parts.length < 3)
|
||||
return null;
|
||||
return `${parts[0]}_${parts[1]}`;
|
||||
}
|
||||
function hashApiKey(rawKey) {
|
||||
return crypto_1.default.createHash('sha256').update(rawKey).digest('hex');
|
||||
}
|
||||
function timingSafeEqualHex(a, b) {
|
||||
const left = Buffer.from(a, 'hex');
|
||||
const right = Buffer.from(b, 'hex');
|
||||
if (left.length !== right.length)
|
||||
return false;
|
||||
return crypto_1.default.timingSafeEqual(left, right);
|
||||
}
|
||||
//# sourceMappingURL=apiKeys.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"apiKeys.js","sourceRoot":"","sources":["../../src/security/apiKeys.ts"],"names":[],"mappings":";;;;;AAEA,sDAKC;AAED,0CAIC;AAED,gCAEC;AAED,gDAKC;AAxBD,oDAA2B;AAE3B,SAAgB,qBAAqB;IACnC,MAAM,MAAM,GAAG,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,OAAO,gBAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAA;IAC7D,MAAM,MAAM,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAA;IACpC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAA;AACxD,CAAC;AAED,SAAgB,eAAe,CAAC,MAAc;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC/B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACjC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AAClC,CAAC;AAED,SAAgB,UAAU,CAAC,MAAc;IACvC,OAAO,gBAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACjE,CAAC;AAED,SAAgB,kBAAkB,CAAC,CAAS,EAAE,CAAS;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACnC,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAC9C,OAAO,gBAAM,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AAC5C,CAAC"}
|
||||
@@ -0,0 +1,10 @@
|
||||
export declare const AdminPolicy: {
|
||||
readonly impersonateCompany: readonly ["SUPER_ADMIN"];
|
||||
readonly manageAdmins: readonly ["SUPER_ADMIN", "ADMIN"];
|
||||
readonly managePricingPlans: readonly ["SUPER_ADMIN", "ADMIN"];
|
||||
readonly manageSubscriptions: readonly ["SUPER_ADMIN", "ADMIN", "FINANCE"];
|
||||
readonly performRefund: readonly ["SUPER_ADMIN", "ADMIN", "FINANCE"];
|
||||
readonly viewSensitiveCredentials: readonly ["SUPER_ADMIN"];
|
||||
};
|
||||
export type AdminPolicyAction = keyof typeof AdminPolicy;
|
||||
//# sourceMappingURL=adminPolicy.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"adminPolicy.d.ts","sourceRoot":"","sources":["../../../src/security/policies/adminPolicy.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW;;;;;;;CAOiC,CAAA;AAEzD,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,WAAW,CAAA"}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AdminPolicy = void 0;
|
||||
exports.AdminPolicy = {
|
||||
impersonateCompany: ['SUPER_ADMIN'],
|
||||
manageAdmins: ['SUPER_ADMIN', 'ADMIN'],
|
||||
managePricingPlans: ['SUPER_ADMIN', 'ADMIN'],
|
||||
manageSubscriptions: ['SUPER_ADMIN', 'ADMIN', 'FINANCE'],
|
||||
performRefund: ['SUPER_ADMIN', 'ADMIN', 'FINANCE'],
|
||||
viewSensitiveCredentials: ['SUPER_ADMIN'],
|
||||
};
|
||||
//# sourceMappingURL=adminPolicy.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"adminPolicy.js","sourceRoot":"","sources":["../../../src/security/policies/adminPolicy.ts"],"names":[],"mappings":";;;AAEa,QAAA,WAAW,GAAG;IACzB,kBAAkB,EAAE,CAAC,aAAa,CAAC;IACnC,YAAY,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC;IACtC,kBAAkB,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC;IAC5C,mBAAmB,EAAE,CAAC,aAAa,EAAE,OAAO,EAAE,SAAS,CAAC;IACxD,aAAa,EAAE,CAAC,aAAa,EAAE,OAAO,EAAE,SAAS,CAAC;IAClD,wBAAwB,EAAE,CAAC,aAAa,CAAC;CACc,CAAA"}
|
||||
@@ -0,0 +1,12 @@
|
||||
export declare const CompanyPolicy: {
|
||||
readonly viewApiKey: readonly ["OWNER"];
|
||||
readonly regenerateApiKey: readonly ["OWNER"];
|
||||
readonly updateAccountingSettings: readonly ["OWNER", "MANAGER"];
|
||||
readonly deletePricingRule: readonly ["OWNER", "MANAGER"];
|
||||
readonly managePricingRules: readonly ["OWNER", "MANAGER"];
|
||||
readonly manageTeam: readonly ["OWNER", "MANAGER"];
|
||||
readonly viewBilling: readonly ["OWNER", "MANAGER"];
|
||||
readonly manageBilling: readonly ["OWNER"];
|
||||
};
|
||||
export type CompanyPolicyAction = keyof typeof CompanyPolicy;
|
||||
//# sourceMappingURL=companyPolicy.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"companyPolicy.d.ts","sourceRoot":"","sources":["../../../src/security/policies/companyPolicy.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa;;;;;;;;;CASkC,CAAA;AAE5D,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,aAAa,CAAA"}
|
||||
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CompanyPolicy = void 0;
|
||||
exports.CompanyPolicy = {
|
||||
viewApiKey: ['OWNER'],
|
||||
regenerateApiKey: ['OWNER'],
|
||||
updateAccountingSettings: ['OWNER', 'MANAGER'],
|
||||
deletePricingRule: ['OWNER', 'MANAGER'],
|
||||
managePricingRules: ['OWNER', 'MANAGER'],
|
||||
manageTeam: ['OWNER', 'MANAGER'],
|
||||
viewBilling: ['OWNER', 'MANAGER'],
|
||||
manageBilling: ['OWNER'],
|
||||
};
|
||||
//# sourceMappingURL=companyPolicy.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"companyPolicy.js","sourceRoot":"","sources":["../../../src/security/policies/companyPolicy.ts"],"names":[],"mappings":";;;AAEa,QAAA,aAAa,GAAG;IAC3B,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,gBAAgB,EAAE,CAAC,OAAO,CAAC;IAC3B,wBAAwB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IAC9C,iBAAiB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IACvC,kBAAkB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IACxC,UAAU,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IAChC,WAAW,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IACjC,aAAa,EAAE,CAAC,OAAO,CAAC;CACkC,CAAA"}
|
||||
@@ -0,0 +1,7 @@
|
||||
export declare function generatePublicAccessToken(): {
|
||||
token: string;
|
||||
tokenHash: string;
|
||||
};
|
||||
export declare function hashPublicAccessToken(token: string): string;
|
||||
export declare function timingSafeEqualTokenHash(a: string, b: string): boolean;
|
||||
//# sourceMappingURL=publicAccessTokens.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"publicAccessTokens.d.ts","sourceRoot":"","sources":["../../src/security/publicAccessTokens.ts"],"names":[],"mappings":"AAEA,wBAAgB,yBAAyB;;;EAGxC;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,UAElD;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,WAK5D"}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.generatePublicAccessToken = generatePublicAccessToken;
|
||||
exports.hashPublicAccessToken = hashPublicAccessToken;
|
||||
exports.timingSafeEqualTokenHash = timingSafeEqualTokenHash;
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
function generatePublicAccessToken() {
|
||||
const token = crypto_1.default.randomBytes(32).toString('base64url');
|
||||
return { token, tokenHash: hashPublicAccessToken(token) };
|
||||
}
|
||||
function hashPublicAccessToken(token) {
|
||||
return crypto_1.default.createHash('sha256').update(token).digest('hex');
|
||||
}
|
||||
function timingSafeEqualTokenHash(a, b) {
|
||||
const left = Buffer.from(a, 'hex');
|
||||
const right = Buffer.from(b, 'hex');
|
||||
if (left.length !== right.length)
|
||||
return false;
|
||||
return crypto_1.default.timingSafeEqual(left, right);
|
||||
}
|
||||
//# sourceMappingURL=publicAccessTokens.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"publicAccessTokens.js","sourceRoot":"","sources":["../../src/security/publicAccessTokens.ts"],"names":[],"mappings":";;;;;AAEA,8DAGC;AAED,sDAEC;AAED,4DAKC;AAhBD,oDAA2B;AAE3B,SAAgB,yBAAyB;IACvC,MAAM,KAAK,GAAG,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC1D,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAA;AAC3D,CAAC;AAED,SAAgB,qBAAqB,CAAC,KAAa;IACjD,OAAO,gBAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAChE,CAAC;AAED,SAAgB,wBAAwB,CAAC,CAAS,EAAE,CAAS;IAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACnC,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAC9C,OAAO,gBAAM,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AAC5C,CAAC"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import type { Response } from 'express';
|
||||
import type { ActorType } from './tokens';
|
||||
export declare function getSessionCookieName(actorType: ActorType): string;
|
||||
export declare function setSessionCookie(res: Response, actorType: ActorType, token: string, maxAgeMs?: number): void;
|
||||
export declare function clearSessionCookie(res: Response, actorType: ActorType): void;
|
||||
//# sourceMappingURL=sessionCookies.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sessionCookies.d.ts","sourceRoot":"","sources":["../../src/security/sessionCookies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAQzC,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,UAExD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,QAQrG;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,QAOrE"}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getSessionCookieName = getSessionCookieName;
|
||||
exports.setSessionCookie = setSessionCookie;
|
||||
exports.clearSessionCookie = clearSessionCookie;
|
||||
const COOKIE_NAMES = {
|
||||
admin: 'admin_session',
|
||||
employee: 'employee_session',
|
||||
renter: 'renter_session',
|
||||
};
|
||||
function getSessionCookieName(actorType) {
|
||||
return COOKIE_NAMES[actorType];
|
||||
}
|
||||
function setSessionCookie(res, actorType, token, maxAgeMs) {
|
||||
res.cookie(getSessionCookieName(actorType), token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: actorType === 'admin' ? 'strict' : 'lax',
|
||||
path: '/',
|
||||
maxAge: maxAgeMs,
|
||||
});
|
||||
}
|
||||
function clearSessionCookie(res, actorType) {
|
||||
res.clearCookie(getSessionCookieName(actorType), {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: actorType === 'admin' ? 'strict' : 'lax',
|
||||
path: '/',
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=sessionCookies.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sessionCookies.js","sourceRoot":"","sources":["../../src/security/sessionCookies.ts"],"names":[],"mappings":";;AASA,oDAEC;AAED,4CAQC;AAED,gDAOC;AA3BD,MAAM,YAAY,GAA8B;IAC9C,KAAK,EAAE,eAAe;IACtB,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,gBAAgB;CACzB,CAAA;AAED,SAAgB,oBAAoB,CAAC,SAAoB;IACvD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAA;AAChC,CAAC;AAED,SAAgB,gBAAgB,CAAC,GAAa,EAAE,SAAoB,EAAE,KAAa,EAAE,QAAiB;IACpG,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE;QACjD,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAC7C,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;QAClD,IAAI,EAAE,GAAG;QACT,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAA;AACJ,CAAC;AAED,SAAgB,kBAAkB,CAAC,GAAa,EAAE,SAAoB;IACpE,GAAG,CAAC,WAAW,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE;QAC/C,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAC7C,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;QAClD,IAAI,EAAE,GAAG;KACV,CAAC,CAAA;AACJ,CAAC"}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
export type ActorType = 'admin' | 'employee' | 'renter';
|
||||
export type ActorTokenPayload = jwt.JwtPayload & {
|
||||
sub: string;
|
||||
type: ActorType;
|
||||
sessionVersion?: number;
|
||||
last2faAt?: number;
|
||||
};
|
||||
export declare function signActorToken(actorId: string, actorType: ActorType, options?: Pick<jwt.SignOptions, 'expiresIn'> & {
|
||||
sessionVersion?: number;
|
||||
last2faAt?: number;
|
||||
}): string;
|
||||
export declare function verifyActorToken(token: string, expectedType: ActorType): ActorTokenPayload;
|
||||
export declare function verifyAnyActorToken(token: string): ActorTokenPayload;
|
||||
//# sourceMappingURL=tokens.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/security/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAA;AAE9B,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEvD,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,UAAU,GAAG;IAC/C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,SAAS,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAWD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,UAkBnG;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,GAAG,iBAAiB,CAY1F;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CASpE"}
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.signActorToken = signActorToken;
|
||||
exports.verifyActorToken = verifyActorToken;
|
||||
exports.verifyAnyActorToken = verifyAnyActorToken;
|
||||
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
||||
const ISSUER = 'rentaldrivego-api';
|
||||
const ALG = ['HS256'];
|
||||
function getJwtSecret() {
|
||||
const secret = process.env.JWT_SECRET;
|
||||
if (!secret)
|
||||
throw new Error('JWT_SECRET is required');
|
||||
return secret;
|
||||
}
|
||||
function signActorToken(actorId, actorType, options = {}) {
|
||||
const { sessionVersion, last2faAt, ...signOptions } = options;
|
||||
return jsonwebtoken_1.default.sign({
|
||||
sub: actorId,
|
||||
type: actorType,
|
||||
...(sessionVersion === undefined ? {} : { sessionVersion }),
|
||||
...(last2faAt === undefined ? {} : { last2faAt }),
|
||||
}, getJwtSecret(), {
|
||||
algorithm: 'HS256',
|
||||
issuer: ISSUER,
|
||||
audience: actorType,
|
||||
expiresIn: signOptions.expiresIn ?? '8h',
|
||||
});
|
||||
}
|
||||
function verifyActorToken(token, expectedType) {
|
||||
const payload = jsonwebtoken_1.default.verify(token, getJwtSecret(), {
|
||||
algorithms: ALG,
|
||||
issuer: ISSUER,
|
||||
audience: expectedType,
|
||||
});
|
||||
if (typeof payload.sub !== 'string' || payload.type !== expectedType) {
|
||||
throw new Error('Invalid actor token');
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
function verifyAnyActorToken(token) {
|
||||
const decoded = jsonwebtoken_1.default.decode(token);
|
||||
const actorType = decoded?.type;
|
||||
if (actorType !== 'admin' && actorType !== 'employee' && actorType !== 'renter') {
|
||||
throw new Error('Invalid actor token');
|
||||
}
|
||||
return verifyActorToken(token, actorType);
|
||||
}
|
||||
//# sourceMappingURL=tokens.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/security/tokens.ts"],"names":[],"mappings":";;;;;AAoBA,wCAqBC;AAED,4CAYC;AAED,kDASC;AAlED,gEAA8B;AAW9B,MAAM,MAAM,GAAG,mBAAmB,CAAA;AAClC,MAAM,GAAG,GAAoB,CAAC,OAAO,CAAC,CAAA;AAEtC,SAAS,YAAY;IACnB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAA;IACrC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;IACtD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAgB,cAAc,CAC5B,OAAe,EACf,SAAoB,EACpB,UAAgG,EAAE;IAElG,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAA;IAC7D,OAAO,sBAAG,CAAC,IAAI,CACb;QACE,GAAG,EAAE,OAAO;QACZ,IAAI,EAAE,SAAS;QACf,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC;QAC3D,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;KAClD,EACD,YAAY,EAAE,EACd;QACE,SAAS,EAAE,OAAO;QAClB,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,SAAS;QACnB,SAAS,EAAE,WAAW,CAAC,SAAS,IAAI,IAAI;KACzC,CACF,CAAA;AACH,CAAC;AAED,SAAgB,gBAAgB,CAAC,KAAa,EAAE,YAAuB;IACrE,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE;QAChD,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,YAAY;KACvB,CAAmB,CAAA;IAEpB,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;IACxC,CAAC;IAED,OAAO,OAA4B,CAAA;AACrC,CAAC;AAED,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,CAA0B,CAAA;IAC1D,MAAM,SAAS,GAAG,OAAO,EAAE,IAAI,CAAA;IAE/B,IAAI,SAAS,KAAK,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChF,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;IACxC,CAAC;IAED,OAAO,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AAC3C,CAAC"}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
type WebhookProcessInput<T> = {
|
||||
provider: string;
|
||||
providerEventId: string;
|
||||
eventType: string;
|
||||
rawBody: string | Buffer;
|
||||
handle: () => Promise<T>;
|
||||
};
|
||||
export declare function processWebhookOnce<T>({ provider, providerEventId, eventType, rawBody, handle, }: WebhookProcessInput<T>): Promise<{
|
||||
duplicate: boolean;
|
||||
result?: T;
|
||||
}>;
|
||||
export declare function getWebhookEventId(provider: 'amanpay' | 'paypal', payload: any): string;
|
||||
export {};
|
||||
//# sourceMappingURL=webhookIdempotency.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"webhookIdempotency.d.ts","sourceRoot":"","sources":["../../src/security/webhookIdempotency.ts"],"names":[],"mappings":"AAIA,KAAK,mBAAmB,CAAC,CAAC,IAAI;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;CACzB,CAAA;AAmCD,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,EAC1C,QAAQ,EACR,eAAe,EACf,SAAS,EACT,OAAO,EACP,MAAM,GACP,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC,CAsCtE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,UAK7E"}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.processWebhookOnce = processWebhookOnce;
|
||||
exports.getWebhookEventId = getWebhookEventId;
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
function payloadHash(rawBody) {
|
||||
return crypto_1.default.createHash('sha256').update(rawBody).digest('hex');
|
||||
}
|
||||
function getWebhookEventModel() {
|
||||
try {
|
||||
// Lazy load keeps isolated unit tests from requiring a generated Prisma client
|
||||
// before `prisma generate` has run in the local sandbox.
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { prisma } = require('../lib/prisma');
|
||||
return prisma.webhookEvent;
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
async function findExisting(model, provider, providerEventId) {
|
||||
return model.findUnique({
|
||||
where: { provider_providerEventId: { provider, providerEventId } },
|
||||
});
|
||||
}
|
||||
async function mark(model, id, status, errorMessage) {
|
||||
await model.update({
|
||||
where: { id },
|
||||
data: {
|
||||
status,
|
||||
processedAt: status === 'PROCESSED' ? new Date() : undefined,
|
||||
errorMessage: errorMessage?.slice(0, 1000),
|
||||
},
|
||||
});
|
||||
}
|
||||
async function processWebhookOnce({ provider, providerEventId, eventType, rawBody, handle, }) {
|
||||
const model = getWebhookEventModel();
|
||||
if (!model) {
|
||||
const result = await handle();
|
||||
return { duplicate: false, result };
|
||||
}
|
||||
const hash = payloadHash(rawBody);
|
||||
let eventRecord;
|
||||
try {
|
||||
eventRecord = await model.create({
|
||||
data: {
|
||||
provider,
|
||||
providerEventId,
|
||||
eventType,
|
||||
status: 'PROCESSING',
|
||||
payloadHash: hash,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
const existing = await findExisting(model, provider, providerEventId);
|
||||
if (existing?.status === 'PROCESSED')
|
||||
return { duplicate: true };
|
||||
if (existing?.payloadHash && existing.payloadHash !== hash) {
|
||||
throw new Error('Webhook event ID replayed with a different payload hash');
|
||||
}
|
||||
if (existing?.status === 'PROCESSING')
|
||||
return { duplicate: true };
|
||||
eventRecord = existing;
|
||||
}
|
||||
try {
|
||||
const result = await handle();
|
||||
if (eventRecord?.id)
|
||||
await mark(model, eventRecord.id, 'PROCESSED');
|
||||
return { duplicate: false, result };
|
||||
}
|
||||
catch (err) {
|
||||
if (eventRecord?.id)
|
||||
await mark(model, eventRecord.id, 'FAILED', err?.message);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
function getWebhookEventId(provider, payload) {
|
||||
if (provider === 'paypal') {
|
||||
return String(payload.id ?? payload.event_id ?? `${payload.event_type ?? 'unknown'}:${payload.resource?.id ?? 'missing'}`);
|
||||
}
|
||||
return String(payload.event_id ?? payload.id ?? payload.transaction_id ?? 'missing');
|
||||
}
|
||||
//# sourceMappingURL=webhookIdempotency.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"webhookIdempotency.js","sourceRoot":"","sources":["../../src/security/webhookIdempotency.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user