@authyon/server 0.2.0-beta.3 → 0.2.0-beta.4

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/dist/index.js CHANGED
@@ -1,10 +1,22 @@
1
- var __typeError = (msg) => {
2
- throw TypeError(msg);
3
- };
4
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6
- var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
1
+ import {
2
+ AuthorizationError,
3
+ AuthyonAbility,
4
+ AuthyonAbilityBuilder,
5
+ JwksTokenVerifier,
6
+ TokenVerificationError,
7
+ __privateAdd,
8
+ __privateGet,
9
+ __privateSet,
10
+ authorizeRequest,
11
+ authorizeToken,
12
+ clientIpHeaders,
13
+ createAuthorizationErrorResponse,
14
+ createAuthyonAbility,
15
+ createAuthyonRules,
16
+ createExpressAuthorizationMiddleware,
17
+ hasPermission,
18
+ normalizeClientIp
19
+ } from "./chunk-24IFDJ73.js";
8
20
 
9
21
  // src/client/credentialManagement.ts
10
22
  function segment(value) {
@@ -592,167 +604,6 @@ var JsonHttpClient = class {
592
604
  }
593
605
  };
594
606
 
595
- // ../../internal/core/http/clientIp.ts
596
- function clientIpHeaders(context) {
597
- if (!context?.clientIp) return {};
598
- return { "X-Forwarded-For": normalizeClientIp(context.clientIp) };
599
- }
600
- function normalizeClientIp(value) {
601
- let ip = value.trim();
602
- if (ip.startsWith("[") && ip.endsWith("]")) ip = ip.slice(1, -1);
603
- if (!ip || ip.length > 45 || ip.includes(",") || /[\r\n\s]/.test(ip) || !/^[0-9a-f:.]+$/i.test(ip) || !ip.includes(".") && !ip.includes(":")) {
604
- throw invalidClientIp();
605
- }
606
- try {
607
- new URL(ip.includes(":") ? `http://[${ip}]` : `http://${ip}`);
608
- } catch {
609
- throw invalidClientIp();
610
- }
611
- return ip;
612
- }
613
- function invalidClientIp() {
614
- return new Error("Authyon: `clientIp` must be a single valid IPv4 or IPv6 address");
615
- }
616
-
617
- // src/security/jwksTokenVerifier.ts
618
- import {
619
- createLocalJWKSet,
620
- createRemoteJWKSet,
621
- jwtVerify
622
- } from "jose";
623
- var SAFE_ASYMMETRIC_ALGORITHMS = [
624
- "RS256",
625
- "PS256",
626
- "ES256",
627
- "EdDSA"
628
- ];
629
- var TokenVerificationError = class extends Error {
630
- constructor(code, options = {}) {
631
- super(messageForCode(code));
632
- this.code = code;
633
- this.name = "TokenVerificationError";
634
- if (options.cause !== void 0) Object.defineProperty(this, "cause", { value: options.cause });
635
- }
636
- };
637
- var JwksTokenVerifier = class {
638
- constructor(options) {
639
- this.options = options;
640
- validateOptions(options);
641
- this.algorithms = [...options.algorithms ?? ["RS256"]];
642
- this.acceptedTokenTypes = new Set(options.acceptedTokenTypes ?? ["JWT", "at+jwt"]);
643
- this.verifyKey = options.jwks ? createLocalJWKSet(options.jwks) : createRemoteJWKSet(new URL(options.jwksUri), {
644
- timeoutDuration: options.jwksTimeoutMs ?? 5e3,
645
- cacheMaxAge: options.jwksCacheMs ?? 6e5,
646
- cooldownDuration: options.jwksCooldownMs ?? 3e4
647
- });
648
- }
649
- async verifyAccessToken(token) {
650
- if (!token || token.length > 16384) {
651
- throw new TokenVerificationError("token.invalid");
652
- }
653
- try {
654
- const result = await jwtVerify(token, this.verifyKey, {
655
- algorithms: this.algorithms,
656
- issuer: this.options.issuer,
657
- audience: this.options.audience,
658
- clockTolerance: this.options.clockToleranceSeconds ?? 5,
659
- maxTokenAge: this.options.maxTokenAgeSeconds ?? 3600,
660
- requiredClaims: ["iss", "aud", "sub", "exp", "iat"]
661
- });
662
- validateTokenType(result, this.acceptedTokenTypes);
663
- validateClaims(result.payload);
664
- return { claims: result.payload, protectedHeader: result.protectedHeader };
665
- } catch (cause) {
666
- if (cause instanceof TokenVerificationError) throw cause;
667
- throw new TokenVerificationError(errorCode(cause), { cause });
668
- }
669
- }
670
- };
671
- function validateTokenType(result, acceptedTokenTypes) {
672
- const type = result.protectedHeader.typ;
673
- if (!type || !acceptedTokenTypes.has(type)) {
674
- throw new TokenVerificationError("token.invalid_claims");
675
- }
676
- }
677
- function validateClaims(claims) {
678
- if (typeof claims.sub !== "string" || !claims.sub) invalidClaims();
679
- if (claims.permissions !== void 0 && !isStringArray(claims.permissions)) invalidClaims();
680
- if (claims.roles !== void 0 && !isStringArray(claims.roles)) invalidClaims();
681
- if (claims.scope !== void 0 && typeof claims.scope !== "string") invalidClaims();
682
- }
683
- function validateOptions(options) {
684
- const issuer = secureUrl(options.issuer, "issuer");
685
- if (Boolean(options.jwksUri) === Boolean(options.jwks)) {
686
- throw new Error("Authyon: configure exactly one of `jwksUri` or `jwks`");
687
- }
688
- const jwks = options.jwksUri ? secureUrl(options.jwksUri, "jwksUri") : void 0;
689
- if (!options.audience || Array.isArray(options.audience) && options.audience.length === 0) {
690
- throw new Error("Authyon: JWT `audience` is required");
691
- }
692
- if (jwks && issuer.origin !== jwks.origin) {
693
- throw new Error("Authyon: `issuer` and `jwksUri` must use the same trusted origin");
694
- }
695
- const algorithms = options.algorithms ?? ["RS256"];
696
- if (algorithms.length === 0 || algorithms.some((algorithm) => !SAFE_ASYMMETRIC_ALGORITHMS.includes(algorithm))) {
697
- throw new Error("Authyon: only approved asymmetric JWT algorithms are accepted");
698
- }
699
- if (options.acceptedTokenTypes?.length === 0 || options.acceptedTokenTypes?.some((type) => !type.trim())) {
700
- throw new Error("Authyon: `acceptedTokenTypes` must contain non-empty values");
701
- }
702
- for (const [name, value] of [
703
- ["clockToleranceSeconds", options.clockToleranceSeconds],
704
- ["maxTokenAgeSeconds", options.maxTokenAgeSeconds],
705
- ["jwksTimeoutMs", options.jwksTimeoutMs],
706
- ["jwksCacheMs", options.jwksCacheMs],
707
- ["jwksCooldownMs", options.jwksCooldownMs]
708
- ]) {
709
- if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
710
- throw new Error(`Authyon: \`${name}\` must be a non-negative finite number`);
711
- }
712
- }
713
- }
714
- function secureUrl(value, name) {
715
- let url;
716
- try {
717
- url = new URL(value);
718
- } catch {
719
- throw new Error(`Authyon: JWT \`${name}\` must be an absolute URL`);
720
- }
721
- if (url.protocol !== "https:" || url.username || url.password || url.hash) {
722
- throw new Error(`Authyon: JWT \`${name}\` must be a credential-free HTTPS URL`);
723
- }
724
- return url;
725
- }
726
- function errorCode(error) {
727
- const code = isRecord(error) && typeof error.code === "string" ? error.code : "";
728
- if (code === "ERR_JWT_EXPIRED") return "token.expired";
729
- if (code === "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") return "token.invalid_signature";
730
- if (code === "ERR_JWKS_TIMEOUT") {
731
- return "token.key_unavailable";
732
- }
733
- if (code === "ERR_JWKS_NO_MATCHING_KEY") return "token.invalid_signature";
734
- if (code === "ERR_JWT_CLAIM_VALIDATION_FAILED") {
735
- const claim = isRecord(error) && typeof error.claim === "string" ? error.claim : "";
736
- return claim === "nbf" ? "token.not_active" : "token.invalid_claims";
737
- }
738
- return "token.invalid";
739
- }
740
- function messageForCode(code) {
741
- if (code === "token.key_unavailable") return "Token verification key is unavailable";
742
- if (code === "token.expired") return "Token has expired";
743
- if (code === "token.not_active") return "Token is not active yet";
744
- return "Token verification failed";
745
- }
746
- function invalidClaims() {
747
- throw new TokenVerificationError("token.invalid_claims");
748
- }
749
- function isStringArray(value) {
750
- return Array.isArray(value) && value.every((item) => typeof item === "string");
751
- }
752
- function isRecord(value) {
753
- return typeof value === "object" && value !== null;
754
- }
755
-
756
607
  // src/client/authyonServerClient.ts
757
608
  var _clientSecret;
758
609
  var AuthyonServerClient = class {
@@ -1293,308 +1144,6 @@ var AuthyonServerClientBuilder = class {
1293
1144
  }
1294
1145
  };
1295
1146
  _options = new WeakMap();
1296
-
1297
- // ../../internal/core/authorization/ability.ts
1298
- var AuthyonAbility = class {
1299
- constructor(rules = [], detectSubjectType = defaultSubjectType) {
1300
- this.detectSubjectType = detectSubjectType;
1301
- this.listeners = /* @__PURE__ */ new Set();
1302
- this.currentRules = rules.map(cloneRule);
1303
- }
1304
- get rules() {
1305
- return this.currentRules;
1306
- }
1307
- can(action, subject, field) {
1308
- const subjectType = typeof subject === "string" ? subject : this.detectSubjectType(subject);
1309
- for (let index = this.currentRules.length - 1; index >= 0; index -= 1) {
1310
- const rule = this.currentRules[index];
1311
- if (!matchesToken(rule.action, action, "manage")) continue;
1312
- if (!matchesToken(rule.subject, subjectType, "all")) continue;
1313
- if (field && rule.fields && !rule.fields.some((value) => matchesField(value, field)))
1314
- continue;
1315
- if (rule.conditions) {
1316
- if (typeof subject === "string" || !matchesConditions(subject, rule.conditions)) continue;
1317
- }
1318
- return !rule.inverted;
1319
- }
1320
- return false;
1321
- }
1322
- cannot(action, subject, field) {
1323
- return !this.can(action, subject, field);
1324
- }
1325
- rulesFor(action, subject) {
1326
- return this.currentRules.filter(
1327
- (rule) => matchesToken(rule.action, action, "manage") && matchesToken(rule.subject, subject, "all")
1328
- );
1329
- }
1330
- update(rules) {
1331
- this.currentRules = rules.map(cloneRule);
1332
- for (const listener of this.listeners) listener(this.rules);
1333
- }
1334
- on(event, listener) {
1335
- if (event !== "updated") return () => void 0;
1336
- this.listeners.add(listener);
1337
- return () => this.listeners.delete(listener);
1338
- }
1339
- };
1340
- var AuthyonAbilityBuilder = class {
1341
- constructor() {
1342
- this.rules = [];
1343
- }
1344
- can(action, subject, conditions, fields) {
1345
- this.rules.push({ action, subject, conditions, fields });
1346
- return this;
1347
- }
1348
- cannot(action, subject, conditions, fields, reason) {
1349
- this.rules.push({ action, subject, conditions, fields, reason, inverted: true });
1350
- return this;
1351
- }
1352
- build(options = {}) {
1353
- return new AuthyonAbility(this.rules, options.detectSubjectType);
1354
- }
1355
- };
1356
- function createAuthyonAbility(source = {}, options = {}) {
1357
- return new AuthyonAbility(createAuthyonRules(source, options), options.detectSubjectType);
1358
- }
1359
- function hasPermission(source, requiredPermission) {
1360
- const requirement = permissionToRule(requiredPermission);
1361
- if (!requirement || typeof requirement.action !== "string" || typeof requirement.subject !== "string") {
1362
- return false;
1363
- }
1364
- const permissionSource = isPermissionList(source) ? { permissions: source } : source;
1365
- return createAuthyonAbility(permissionSource).can(requirement.action, requirement.subject);
1366
- }
1367
- function isPermissionList(source) {
1368
- return Array.isArray(source);
1369
- }
1370
- function createAuthyonRules(source = {}, options = {}) {
1371
- const permissions = /* @__PURE__ */ new Set([
1372
- ...source.permissions ?? [],
1373
- ...source.scope?.split(/\s+/).filter(Boolean) ?? []
1374
- ]);
1375
- const rules = [...permissions].map(permissionToRule).filter(isAbilityRule);
1376
- for (const role of /* @__PURE__ */ new Set([...source.roles ?? [], ...options.roles ?? []])) {
1377
- rules.push(...(options.roleRules?.[role] ?? []).map(cloneRule));
1378
- }
1379
- rules.push(...(options.rules ?? []).map(cloneRule));
1380
- return rules;
1381
- }
1382
- function permissionToRule(permission) {
1383
- const normalized = permission.trim();
1384
- if (!normalized) return null;
1385
- if (normalized === "*" || normalized === "*:*" || normalized === "all:manage") {
1386
- return { action: "manage", subject: "all" };
1387
- }
1388
- const separator = normalized.lastIndexOf(":");
1389
- if (separator <= 0 || separator === normalized.length - 1) return null;
1390
- const subject = normalized.slice(0, separator);
1391
- const action = normalized.slice(separator + 1);
1392
- return {
1393
- action: action === "*" ? "manage" : action,
1394
- subject: subject === "*" ? "all" : subject
1395
- };
1396
- }
1397
- function matchesToken(value, expected, wildcard) {
1398
- return (Array.isArray(value) ? value : [value]).some(
1399
- (candidate) => candidate === expected || candidate === wildcard || candidate === "*" || matchesSegments(candidate, expected)
1400
- );
1401
- }
1402
- function matchesSegments(pattern, value) {
1403
- const patternSegments = pattern.split(":");
1404
- const valueSegments = value.split(":");
1405
- return patternSegments.length === valueSegments.length && patternSegments.every((segment2, index) => segment2 === "*" || segment2 === valueSegments[index]);
1406
- }
1407
- function matchesField(pattern, field) {
1408
- if (pattern === "*" || pattern === field) return true;
1409
- return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
1410
- }
1411
- function matchesConditions(subject, conditions) {
1412
- return Object.entries(conditions).every(([path, expected]) => {
1413
- if (path === "$and" && Array.isArray(expected)) {
1414
- return expected.every(
1415
- (condition) => matchesConditions(subject, condition)
1416
- );
1417
- }
1418
- if (path === "$or" && Array.isArray(expected)) {
1419
- return expected.some(
1420
- (condition) => matchesConditions(subject, condition)
1421
- );
1422
- }
1423
- return matchesValue(readPath(subject, path), expected);
1424
- });
1425
- }
1426
- function matchesValue(actual, expected) {
1427
- if (!isRecord2(expected) || !Object.keys(expected).some((key) => key.startsWith("$"))) {
1428
- return isRecord2(expected) && isRecord2(actual) ? matchesConditions(actual, expected) : Object.is(actual, expected);
1429
- }
1430
- return Object.entries(expected).every(([operator, operand]) => {
1431
- switch (operator) {
1432
- case "$eq":
1433
- return Object.is(actual, operand);
1434
- case "$ne":
1435
- return !Object.is(actual, operand);
1436
- case "$in":
1437
- return Array.isArray(operand) && operand.some((value) => Object.is(actual, value));
1438
- case "$nin":
1439
- return Array.isArray(operand) && !operand.some((value) => Object.is(actual, value));
1440
- case "$gt":
1441
- return typeof actual === "number" && typeof operand === "number" && actual > operand;
1442
- case "$gte":
1443
- return typeof actual === "number" && typeof operand === "number" && actual >= operand;
1444
- case "$lt":
1445
- return typeof actual === "number" && typeof operand === "number" && actual < operand;
1446
- case "$lte":
1447
- return typeof actual === "number" && typeof operand === "number" && actual <= operand;
1448
- case "$exists":
1449
- return operand ? actual !== void 0 : actual === void 0;
1450
- default:
1451
- return false;
1452
- }
1453
- });
1454
- }
1455
- function readPath(value, path) {
1456
- return path.split(".").reduce((current, part) => isRecord2(current) ? current[part] : void 0, value);
1457
- }
1458
- function defaultSubjectType(subject) {
1459
- const explicit = subject.__type ?? subject.type ?? subject.kind;
1460
- if (typeof explicit === "string") return explicit;
1461
- const constructorName = subject.constructor?.name;
1462
- return typeof constructorName === "string" ? constructorName : "Object";
1463
- }
1464
- function cloneRule(rule) {
1465
- return {
1466
- ...rule,
1467
- action: Array.isArray(rule.action) ? [...rule.action] : rule.action,
1468
- subject: Array.isArray(rule.subject) ? [...rule.subject] : rule.subject,
1469
- fields: rule.fields ? [...rule.fields] : void 0
1470
- };
1471
- }
1472
- function isRecord2(value) {
1473
- return typeof value === "object" && value !== null && !Array.isArray(value);
1474
- }
1475
- function isAbilityRule(value) {
1476
- return value !== null;
1477
- }
1478
-
1479
- // src/integrations/authorization.ts
1480
- var AuthorizationError = class extends Error {
1481
- constructor(status, code, message) {
1482
- super(message);
1483
- this.status = status;
1484
- this.code = code;
1485
- this.name = "AuthorizationError";
1486
- }
1487
- };
1488
- async function authorizeToken(client, token, options = {}) {
1489
- if (!token) throw unauthenticated();
1490
- let userId;
1491
- let permissions = [];
1492
- let roles = [];
1493
- let scope;
1494
- if (options.verification === "jwks") {
1495
- if (!options.jwksVerifier) {
1496
- throw new Error("Authyon: `jwksVerifier` is required for JWKS verification");
1497
- }
1498
- let verified;
1499
- try {
1500
- verified = await options.jwksVerifier.verifyAccessToken(token);
1501
- } catch (error) {
1502
- if (!(error instanceof TokenVerificationError)) throw error;
1503
- if (error.code === "token.key_unavailable") {
1504
- throw new AuthorizationError(
1505
- 503,
1506
- "authorization.unavailable",
1507
- "Token verification is temporarily unavailable"
1508
- );
1509
- }
1510
- throw unauthenticated();
1511
- }
1512
- const { claims } = verified;
1513
- userId = claims.sub;
1514
- permissions = claims.permissions ?? [];
1515
- roles = claims.roles ?? [];
1516
- scope = claims.scope;
1517
- } else if (options.verification === "introspect") {
1518
- const identity = await client.introspect(token, { clientIp: options.clientIp });
1519
- if (!identity.active) throw unauthenticated();
1520
- userId = identity.sub;
1521
- permissions = identity.permissions ?? [];
1522
- roles = identity.roles ?? [];
1523
- scope = identity.scope;
1524
- } else {
1525
- const identity = await client.validate(token, { clientIp: options.clientIp });
1526
- if (!identity.valid || !identity.user) throw unauthenticated();
1527
- userId = identity.user.id;
1528
- permissions = identity.user.permissions ?? [];
1529
- }
1530
- const ability = createAuthyonAbility(
1531
- { permissions, roles, scope },
1532
- {
1533
- rules: options.rules,
1534
- roleRules: options.roleRules,
1535
- roles: options.roles,
1536
- detectSubjectType: options.detectSubjectType
1537
- }
1538
- );
1539
- if (options.requirement && !canAccess(ability, options.requirement)) {
1540
- throw new AuthorizationError(403, "authorization.forbidden", "Permission denied");
1541
- }
1542
- return { token, userId, ability, permissions, roles };
1543
- }
1544
- function authorizeRequest(client, request, options = {}) {
1545
- const clientIp = options.resolveClientIp?.(request) ?? options.clientIp;
1546
- return authorizeToken(client, bearerToken(request.headers.get("authorization")), {
1547
- ...options,
1548
- clientIp
1549
- });
1550
- }
1551
- function createExpressAuthorizationMiddleware(client, options = {}) {
1552
- return async (request, response, next) => {
1553
- try {
1554
- const header = request.headers.authorization;
1555
- const clientIp = resolveExpressClientIp(request, options);
1556
- const context = await authorizeToken(
1557
- client,
1558
- bearerToken(Array.isArray(header) ? header[0] : header),
1559
- { ...options, clientIp }
1560
- );
1561
- request[options.requestProperty ?? "authyon"] = context;
1562
- next();
1563
- } catch (error) {
1564
- if (options.passErrorsToNext || !(error instanceof AuthorizationError)) {
1565
- next(error);
1566
- return;
1567
- }
1568
- response.status(error.status).json({ code: error.code, message: error.message });
1569
- }
1570
- };
1571
- }
1572
- function resolveExpressClientIp(request, options) {
1573
- const resolved = options.resolveClientIp?.(request) ?? (options.trustProxy ? request.ip : request.socket?.remoteAddress);
1574
- return resolved ? normalizeClientIp(stripIpv4Prefix(resolved)) : void 0;
1575
- }
1576
- function stripIpv4Prefix(value) {
1577
- return value.startsWith("::ffff:") ? value.slice(7) : value;
1578
- }
1579
- function createAuthorizationErrorResponse(error) {
1580
- if (!(error instanceof AuthorizationError)) throw error;
1581
- return Response.json({ code: error.code, message: error.message }, { status: error.status });
1582
- }
1583
- function canAccess(ability, requirement) {
1584
- return ability.can(requirement.action, requirement.subject, requirement.field);
1585
- }
1586
- function bearerToken(header) {
1587
- const match = header?.match(/^Bearer\s+(.+)$/i);
1588
- if (!match?.[1]) throw unauthenticated();
1589
- return match[1].trim();
1590
- }
1591
- function unauthenticated() {
1592
- return new AuthorizationError(
1593
- 401,
1594
- "authorization.unauthenticated",
1595
- "A valid bearer token is required"
1596
- );
1597
- }
1598
1147
  export {
1599
1148
  AuthorizationError,
1600
1149
  AuthyonAbility,