@absolutejs/auth 0.27.0-beta.7 → 0.27.0-beta.9
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/adaptive/config.d.ts +13 -1
- package/dist/adaptive/fingerprint.d.ts +2 -0
- package/dist/adaptive/types.d.ts +13 -1
- package/dist/audit/export.d.ts +2 -0
- package/dist/audit/integrity.d.ts +5 -1
- package/dist/audit/types.d.ts +1 -0
- package/dist/fga/config.d.ts +8 -0
- package/dist/fga/schema.d.ts +2 -0
- package/dist/fga/types.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +223 -25
- package/dist/index.js.map +14 -11
- package/package.json +1 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { KnownDeviceStore, LoginHistoryStore, RiskAction, RiskAssessment, RiskContext, RiskSignal } from './types';
|
|
1
|
+
import type { KnownDeviceStore, LoginHistoryStore, RiskAction, RiskAssessment, RiskContext, RiskSignal, RiskThresholds, RiskWeights, WeightedRiskAssessment } from './types';
|
|
2
2
|
export type AdaptiveConfig = {
|
|
3
3
|
historyLimit?: number;
|
|
4
4
|
knownDeviceStore: KnownDeviceStore;
|
|
5
5
|
loginHistoryStore: LoginHistoryStore;
|
|
6
6
|
maxTravelKmh?: number;
|
|
7
|
+
offHours?: {
|
|
8
|
+
end: number;
|
|
9
|
+
start: number;
|
|
10
|
+
};
|
|
7
11
|
rules?: Partial<Record<RiskSignal, RiskAction>>;
|
|
8
12
|
velocityMaxAttempts?: number;
|
|
9
13
|
velocityWindowMs?: number;
|
|
@@ -14,9 +18,17 @@ export declare const createRiskEngine: (config: AdaptiveConfig) => {
|
|
|
14
18
|
recordAttempt: (context: RiskContext & {
|
|
15
19
|
outcome: RiskAction;
|
|
16
20
|
}) => Promise<void>;
|
|
21
|
+
scoreRisk: (context: RiskContext, options?: {
|
|
22
|
+
thresholds?: RiskThresholds;
|
|
23
|
+
weights?: RiskWeights;
|
|
24
|
+
}) => Promise<WeightedRiskAssessment>;
|
|
17
25
|
trustDevice: (userId: string, deviceId: string, label?: string) => Promise<void>;
|
|
18
26
|
};
|
|
19
27
|
export declare const recordLoginAttempt: (config: AdaptiveConfig, context: RiskContext & {
|
|
20
28
|
outcome: RiskAction;
|
|
21
29
|
}) => Promise<void>;
|
|
30
|
+
export declare const scoreRisk: (config: AdaptiveConfig & {
|
|
31
|
+
thresholds?: RiskThresholds;
|
|
32
|
+
weights?: RiskWeights;
|
|
33
|
+
}, context: RiskContext) => Promise<WeightedRiskAssessment>;
|
|
22
34
|
export declare const trustDevice: (config: AdaptiveConfig, userId: string, deviceId: string, label?: string) => Promise<void>;
|
package/dist/adaptive/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type RiskAction = 'allow' | 'deny' | 'step_up';
|
|
2
|
-
export type RiskSignal = 'impossible_travel' | 'new_country' | 'new_device' | 'velocity';
|
|
2
|
+
export type RiskSignal = 'impossible_travel' | 'new_country' | 'new_device' | 'off_hours' | 'proxy' | 'velocity';
|
|
3
3
|
export type GeoPoint = {
|
|
4
4
|
country?: string;
|
|
5
5
|
latitude?: number;
|
|
@@ -9,6 +9,8 @@ export type RiskContext = {
|
|
|
9
9
|
deviceId: string;
|
|
10
10
|
geo?: GeoPoint;
|
|
11
11
|
ipAddress?: string;
|
|
12
|
+
isProxy?: boolean;
|
|
13
|
+
localHour?: number;
|
|
12
14
|
now?: number;
|
|
13
15
|
userId: string;
|
|
14
16
|
};
|
|
@@ -20,6 +22,16 @@ export type RiskAssessment = {
|
|
|
20
22
|
action: RiskAction;
|
|
21
23
|
reasons: RiskReason[];
|
|
22
24
|
};
|
|
25
|
+
export type RiskWeights = Partial<Record<RiskSignal, number>>;
|
|
26
|
+
export type RiskThresholds = {
|
|
27
|
+
deny: number;
|
|
28
|
+
stepUp: number;
|
|
29
|
+
};
|
|
30
|
+
export type WeightedRiskAssessment = {
|
|
31
|
+
action: RiskAction;
|
|
32
|
+
reasons: RiskReason[];
|
|
33
|
+
score: number;
|
|
34
|
+
};
|
|
23
35
|
export type KnownDevice = {
|
|
24
36
|
deviceId: string;
|
|
25
37
|
firstSeenAt: number;
|
|
@@ -2,14 +2,18 @@ import type { AuditEvent, AuditSink } from './types';
|
|
|
2
2
|
export type AuditIntegrity = {
|
|
3
3
|
hash: string;
|
|
4
4
|
previousHash: string;
|
|
5
|
+
writerId?: string;
|
|
5
6
|
};
|
|
6
7
|
export type AuditChainResult = {
|
|
7
8
|
brokenAt?: number;
|
|
8
9
|
ok: boolean;
|
|
9
10
|
};
|
|
10
|
-
export declare const createTamperEvidentSink: ({ secret, sink }: {
|
|
11
|
+
export declare const createTamperEvidentSink: ({ loadWriterHead, secret, seedScanLimit, sink, writerId }: {
|
|
12
|
+
loadWriterHead?: (writerId: string) => Promise<string | undefined> | string | undefined;
|
|
11
13
|
secret?: string;
|
|
14
|
+
seedScanLimit?: number;
|
|
12
15
|
sink: AuditSink;
|
|
16
|
+
writerId?: string;
|
|
13
17
|
}) => AuditSink;
|
|
14
18
|
export declare const hashAuditEvent: (event: AuditEvent, previousHash: string, secret?: string) => Promise<string>;
|
|
15
19
|
export declare const verifyAuditChain: (events: AuditEvent[], secret?: string) => Promise<AuditChainResult>;
|
package/dist/audit/types.d.ts
CHANGED
package/dist/fga/config.d.ts
CHANGED
|
@@ -15,10 +15,17 @@ export type Subject = {
|
|
|
15
15
|
subjectId: string;
|
|
16
16
|
subjectType: string;
|
|
17
17
|
};
|
|
18
|
+
export type ObjectQuery = {
|
|
19
|
+
relation: string;
|
|
20
|
+
resourceType: string;
|
|
21
|
+
subjectId: string;
|
|
22
|
+
subjectType: string;
|
|
23
|
+
};
|
|
18
24
|
export declare const check: (config: FgaConfig, query: CheckQuery) => Promise<boolean>;
|
|
19
25
|
export declare const createFgaEngine: (config: FgaConfig) => {
|
|
20
26
|
check: (query: CheckQuery) => Promise<boolean>;
|
|
21
27
|
deleteWarrant: (warrant: Warrant) => Promise<void>;
|
|
28
|
+
listObjects: (query: ObjectQuery) => Promise<string[]>;
|
|
22
29
|
listSubjects: (query: {
|
|
23
30
|
relation: string;
|
|
24
31
|
resourceId: string;
|
|
@@ -27,6 +34,7 @@ export declare const createFgaEngine: (config: FgaConfig) => {
|
|
|
27
34
|
writeWarrant: (warrant: Warrant) => Promise<void>;
|
|
28
35
|
};
|
|
29
36
|
export declare const deleteWarrant: (config: FgaConfig, warrant: Warrant) => Promise<void>;
|
|
37
|
+
export declare const listObjects: (config: FgaConfig, query: ObjectQuery) => Promise<string[]>;
|
|
30
38
|
export declare const listSubjects: (config: FgaConfig, query: {
|
|
31
39
|
relation: string;
|
|
32
40
|
resourceId: string;
|
package/dist/fga/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type Warrant = {
|
|
|
9
9
|
export type WarrantStore = {
|
|
10
10
|
deleteWarrant: (warrant: Warrant) => Promise<void>;
|
|
11
11
|
listForResource: (resourceType: string, resourceId: string, relation: string) => Promise<Warrant[]>;
|
|
12
|
+
listResourceIds: (resourceType: string) => Promise<string[]>;
|
|
12
13
|
saveWarrant: (warrant: Warrant) => Promise<void>;
|
|
13
14
|
};
|
|
14
15
|
export type RelationRule = {
|
package/dist/index.d.ts
CHANGED
|
@@ -14712,6 +14712,7 @@ export { createInMemoryLockoutStore } from './lockout/inMemoryLockoutStore';
|
|
|
14712
14712
|
export { createNeonLockoutStore, createPostgresLockoutStore, lockoutsTable } from './lockout/postgresLockoutStore';
|
|
14713
14713
|
export { createRedisLockoutStore } from './lockout/redisLockoutStore';
|
|
14714
14714
|
export type { RedisLike } from './stores/redis';
|
|
14715
|
+
export { exportAuditCsv } from './audit/export';
|
|
14715
14716
|
export { createInMemoryAuditSink } from './audit/inMemoryAuditStore';
|
|
14716
14717
|
export { auditEventsTable, createNeonAuditSink, createPostgresAuditSink } from './audit/postgresAuditStore';
|
|
14717
14718
|
export * from './sso/types';
|
|
@@ -14736,10 +14737,12 @@ export type { DpopResult } from './oidc/dpop';
|
|
|
14736
14737
|
export { createInMemoryAuthorizationCodeStore, createInMemoryOAuthClientStore, createInMemoryOidcRefreshTokenStore } from './oidc/inMemoryStores';
|
|
14737
14738
|
export { createNeonAuthorizationCodeStore, createNeonOAuthClientStore, createNeonOidcRefreshTokenStore, createPostgresAuthorizationCodeStore, createPostgresOAuthClientStore, createPostgresOidcRefreshTokenStore, oauthClientsTable, oauthCodesTable, oauthRefreshTokensTable } from './oidc/postgresStores';
|
|
14738
14739
|
export * from './adaptive/config';
|
|
14740
|
+
export * from './adaptive/fingerprint';
|
|
14739
14741
|
export * from './adaptive/types';
|
|
14740
14742
|
export { createInMemoryKnownDeviceStore, createInMemoryLoginHistoryStore } from './adaptive/inMemoryStores';
|
|
14741
14743
|
export { createNeonKnownDeviceStore, createNeonLoginHistoryStore, createPostgresKnownDeviceStore, createPostgresLoginHistoryStore, knownDevicesTable, loginHistoryTable } from './adaptive/postgresStores';
|
|
14742
14744
|
export * from './fga/config';
|
|
14745
|
+
export * from './fga/schema';
|
|
14743
14746
|
export * from './fga/types';
|
|
14744
14747
|
export { createInMemoryWarrantStore, warrantKey } from './fga/inMemoryStores';
|
|
14745
14748
|
export { createNeonWarrantStore, createPostgresWarrantStore, warrantsTable } from './fga/postgresStores';
|
package/dist/index.js
CHANGED
|
@@ -18710,6 +18710,7 @@ var INTEGRITY_KEY = "__integrity";
|
|
|
18710
18710
|
var GENESIS = "";
|
|
18711
18711
|
var HEX_RADIX2 = 16;
|
|
18712
18712
|
var HEX_PAD = 2;
|
|
18713
|
+
var DEFAULT_SEED_SCAN_LIMIT = 1000;
|
|
18713
18714
|
var encoder = new TextEncoder;
|
|
18714
18715
|
var toHex = (buffer) => [...new Uint8Array(buffer)].map((byte) => byte.toString(HEX_RADIX2).padStart(HEX_PAD, "0")).join("");
|
|
18715
18716
|
var sha256Hex = async (message) => toHex(await crypto.subtle.digest("SHA-256", encoder.encode(message)));
|
|
@@ -18740,24 +18741,45 @@ var brokenAt = (index) => {
|
|
|
18740
18741
|
return result;
|
|
18741
18742
|
};
|
|
18742
18743
|
var createTamperEvidentSink = ({
|
|
18744
|
+
loadWriterHead,
|
|
18743
18745
|
secret,
|
|
18744
|
-
|
|
18746
|
+
seedScanLimit = DEFAULT_SEED_SCAN_LIMIT,
|
|
18747
|
+
sink,
|
|
18748
|
+
writerId
|
|
18745
18749
|
}) => {
|
|
18750
|
+
const chainWriterId = writerId ?? crypto.randomUUID();
|
|
18751
|
+
const isResuming = writerId !== undefined;
|
|
18746
18752
|
let lastHash;
|
|
18753
|
+
let seeded = false;
|
|
18747
18754
|
const seed = async () => {
|
|
18748
|
-
if (
|
|
18755
|
+
if (seeded)
|
|
18749
18756
|
return;
|
|
18750
|
-
|
|
18751
|
-
|
|
18757
|
+
seeded = true;
|
|
18758
|
+
if (!isResuming) {
|
|
18759
|
+
lastHash = GENESIS;
|
|
18760
|
+
return;
|
|
18761
|
+
}
|
|
18762
|
+
if (loadWriterHead) {
|
|
18763
|
+
lastHash = await loadWriterHead(chainWriterId) ?? GENESIS;
|
|
18764
|
+
return;
|
|
18765
|
+
}
|
|
18766
|
+
const recent = await sink.list?.({ limit: seedScanLimit }) ?? [];
|
|
18767
|
+
const head = recent.find((event) => readIntegrity(event)?.writerId === chainWriterId);
|
|
18768
|
+
lastHash = head ? readIntegrity(head)?.hash ?? GENESIS : GENESIS;
|
|
18752
18769
|
};
|
|
18753
18770
|
return {
|
|
18754
18771
|
list: sink.list,
|
|
18772
|
+
prune: sink.prune,
|
|
18755
18773
|
append: async (event) => {
|
|
18756
18774
|
await seed();
|
|
18757
18775
|
const previousHash = lastHash ?? GENESIS;
|
|
18758
18776
|
const hash = await hashAuditEvent(event, previousHash, secret);
|
|
18759
18777
|
lastHash = hash;
|
|
18760
|
-
const integrity = {
|
|
18778
|
+
const integrity = {
|
|
18779
|
+
hash,
|
|
18780
|
+
previousHash,
|
|
18781
|
+
writerId: chainWriterId
|
|
18782
|
+
};
|
|
18761
18783
|
await sink.append({
|
|
18762
18784
|
...event,
|
|
18763
18785
|
metadata: { ...event.metadata, [INTEGRITY_KEY]: integrity }
|
|
@@ -18773,17 +18795,19 @@ var hashAuditEvent = async (event, previousHash, secret) => {
|
|
|
18773
18795
|
return secret === undefined ? sha256Hex(message) : hmacSha256Hex(secret, message);
|
|
18774
18796
|
};
|
|
18775
18797
|
var verifyAuditChain = async (events, secret) => {
|
|
18776
|
-
|
|
18798
|
+
const heads = new Map;
|
|
18777
18799
|
for (let index = 0;index < events.length; index += 1) {
|
|
18778
18800
|
const event = events[index];
|
|
18779
18801
|
if (event === undefined)
|
|
18780
18802
|
return brokenAt(index);
|
|
18781
18803
|
const integrity = readIntegrity(event);
|
|
18804
|
+
const chain = integrity?.writerId ?? GENESIS;
|
|
18805
|
+
const previousHash = heads.get(chain) ?? GENESIS;
|
|
18782
18806
|
const expected = await hashAuditEvent(event, previousHash, secret);
|
|
18783
18807
|
if (integrity === undefined || integrity.previousHash !== previousHash || integrity.hash !== expected) {
|
|
18784
18808
|
return brokenAt(index);
|
|
18785
18809
|
}
|
|
18786
|
-
|
|
18810
|
+
heads.set(chain, integrity.hash);
|
|
18787
18811
|
}
|
|
18788
18812
|
const valid = { ok: true };
|
|
18789
18813
|
return valid;
|
|
@@ -19061,6 +19085,19 @@ var createRedisLockoutStore = (redis, keyPrefix = DEFAULT_PREFIX) => {
|
|
|
19061
19085
|
}
|
|
19062
19086
|
};
|
|
19063
19087
|
};
|
|
19088
|
+
// src/audit/export.ts
|
|
19089
|
+
var CSV_HEADER = "at,type,userId,ip,organizationId,metadata";
|
|
19090
|
+
var escapeCsv = (value) => /[",\n\r]/u.test(value) ? `"${value.replace(/"/gu, '""')}"` : value;
|
|
19091
|
+
var toRow = (event) => [
|
|
19092
|
+
new Date(event.at).toISOString(),
|
|
19093
|
+
event.type,
|
|
19094
|
+
event.userId ?? "",
|
|
19095
|
+
event.ip ?? "",
|
|
19096
|
+
event.organizationId ?? "",
|
|
19097
|
+
event.metadata ? JSON.stringify(event.metadata) : ""
|
|
19098
|
+
].map(escapeCsv).join(",");
|
|
19099
|
+
var exportAuditCsv = (events) => [CSV_HEADER, ...events.map(toRow)].join(`
|
|
19100
|
+
`);
|
|
19064
19101
|
// src/audit/inMemoryAuditStore.ts
|
|
19065
19102
|
var createInMemoryAuditSink = () => {
|
|
19066
19103
|
const events = [];
|
|
@@ -19073,6 +19110,13 @@ var createInMemoryAuditSink = () => {
|
|
|
19073
19110
|
const ordered = [...matched].sort((left, right) => right.at - left.at);
|
|
19074
19111
|
const limited = filter?.limit === undefined ? ordered : ordered.slice(0, filter.limit);
|
|
19075
19112
|
return limited.map((event) => ({ ...event }));
|
|
19113
|
+
},
|
|
19114
|
+
prune: async (before) => {
|
|
19115
|
+
const kept = events.filter((event) => event.at >= before);
|
|
19116
|
+
const removed = events.length - kept.length;
|
|
19117
|
+
events.length = 0;
|
|
19118
|
+
events.push(...kept);
|
|
19119
|
+
return removed;
|
|
19076
19120
|
}
|
|
19077
19121
|
};
|
|
19078
19122
|
};
|
|
@@ -19114,6 +19158,10 @@ var createPostgresAuditSink = (db) => ({
|
|
|
19114
19158
|
list: async (filter) => {
|
|
19115
19159
|
const rows = await db.select().from(auditEventsTable).where(filter?.userId ? eq(auditEventsTable.user_id, filter.userId) : undefined).orderBy(desc(auditEventsTable.at_ms)).limit(filter?.limit ?? DEFAULT_AUDIT_LIMIT);
|
|
19116
19160
|
return rows.map(toEvent);
|
|
19161
|
+
},
|
|
19162
|
+
prune: async (before) => {
|
|
19163
|
+
const deleted = await db.delete(auditEventsTable).where(lt(auditEventsTable.at_ms, before)).returning({ id: auditEventsTable.id });
|
|
19164
|
+
return deleted.length;
|
|
19117
19165
|
}
|
|
19118
19166
|
});
|
|
19119
19167
|
// src/scim/inMemoryScimTokenStore.ts
|
|
@@ -19550,8 +19598,22 @@ var DEFAULT_RULE_ACTIONS = {
|
|
|
19550
19598
|
impossible_travel: "deny",
|
|
19551
19599
|
new_country: "step_up",
|
|
19552
19600
|
new_device: "step_up",
|
|
19601
|
+
off_hours: "allow",
|
|
19602
|
+
proxy: "step_up",
|
|
19553
19603
|
velocity: "deny"
|
|
19554
19604
|
};
|
|
19605
|
+
var DEFAULT_RISK_WEIGHTS = {
|
|
19606
|
+
impossible_travel: 80,
|
|
19607
|
+
new_country: 25,
|
|
19608
|
+
new_device: 20,
|
|
19609
|
+
off_hours: 10,
|
|
19610
|
+
proxy: 30,
|
|
19611
|
+
velocity: 80
|
|
19612
|
+
};
|
|
19613
|
+
var DEFAULT_OFF_HOURS_START = 0;
|
|
19614
|
+
var DEFAULT_OFF_HOURS_END = 6;
|
|
19615
|
+
var DEFAULT_DENY_SCORE = 80;
|
|
19616
|
+
var DEFAULT_STEP_UP_SCORE = 40;
|
|
19555
19617
|
var toRadians = (degrees) => degrees * Math.PI / DEGREES_PER_HALF_TURN;
|
|
19556
19618
|
var haversineKm = (start, end) => {
|
|
19557
19619
|
if (start.latitude === undefined || start.longitude === undefined || end.latitude === undefined || end.longitude === undefined) {
|
|
@@ -19565,32 +19627,44 @@ var haversineKm = (start, end) => {
|
|
|
19565
19627
|
return HALF * EARTH_RADIUS_KM * Math.asin(Math.sqrt(factor));
|
|
19566
19628
|
};
|
|
19567
19629
|
var mostSevere2 = (reasons) => reasons.reduce((worst, reason) => ACTION_SEVERITY2[reason.action] > ACTION_SEVERITY2[worst] ? reason.action : worst, "allow");
|
|
19568
|
-
var
|
|
19630
|
+
var isWithinOffHours = (hour, range) => {
|
|
19631
|
+
const start = range?.start ?? DEFAULT_OFF_HOURS_START;
|
|
19632
|
+
const end = range?.end ?? DEFAULT_OFF_HOURS_END;
|
|
19633
|
+
if (start <= end)
|
|
19634
|
+
return hour >= start && hour < end;
|
|
19635
|
+
return hour >= start || hour < end;
|
|
19636
|
+
};
|
|
19637
|
+
var actionForScore = (score, thresholds) => {
|
|
19638
|
+
const deny = "deny";
|
|
19639
|
+
const stepUp = "step_up";
|
|
19640
|
+
const allow = "allow";
|
|
19641
|
+
if (score >= thresholds.deny)
|
|
19642
|
+
return deny;
|
|
19643
|
+
if (score >= thresholds.stepUp)
|
|
19644
|
+
return stepUp;
|
|
19645
|
+
return allow;
|
|
19646
|
+
};
|
|
19647
|
+
var detectSignals = async (config, context) => {
|
|
19569
19648
|
const {
|
|
19570
19649
|
historyLimit = DEFAULT_HISTORY_LIMIT,
|
|
19571
19650
|
knownDeviceStore,
|
|
19572
19651
|
loginHistoryStore,
|
|
19573
19652
|
maxTravelKmh = DEFAULT_MAX_TRAVEL_KMH,
|
|
19574
|
-
|
|
19653
|
+
offHours,
|
|
19575
19654
|
velocityMaxAttempts = DEFAULT_VELOCITY_MAX_ATTEMPTS,
|
|
19576
19655
|
velocityWindowMs = DEFAULT_VELOCITY_WINDOW_MS
|
|
19577
19656
|
} = config;
|
|
19578
19657
|
const now = context.now ?? Date.now();
|
|
19579
|
-
const
|
|
19580
|
-
...DEFAULT_RULE_ACTIONS,
|
|
19581
|
-
...rules
|
|
19582
|
-
};
|
|
19583
|
-
const reasons = [];
|
|
19658
|
+
const fired = [];
|
|
19584
19659
|
const [device, history] = await Promise.all([
|
|
19585
19660
|
knownDeviceStore.findDevice(context.userId, context.deviceId),
|
|
19586
19661
|
loginHistoryStore.listRecent(context.userId, historyLimit)
|
|
19587
19662
|
]);
|
|
19588
|
-
if (device === undefined || !device.trusted)
|
|
19589
|
-
|
|
19590
|
-
}
|
|
19663
|
+
if (device === undefined || !device.trusted)
|
|
19664
|
+
fired.push("new_device");
|
|
19591
19665
|
const country = context.geo?.country;
|
|
19592
19666
|
if (country !== undefined && history.length > 0 && !history.some((attempt) => attempt.country === country)) {
|
|
19593
|
-
|
|
19667
|
+
fired.push("new_country");
|
|
19594
19668
|
}
|
|
19595
19669
|
const [previous] = history;
|
|
19596
19670
|
const traveledKm = previous !== undefined && context.geo !== undefined ? haversineKm({
|
|
@@ -19599,20 +19673,34 @@ var assessRisk = async (config, context) => {
|
|
|
19599
19673
|
}, context.geo) : undefined;
|
|
19600
19674
|
const hours = previous === undefined ? 0 : (now - previous.timestamp) / MILLISECONDS_IN_AN_HOUR;
|
|
19601
19675
|
if (traveledKm !== undefined && hours > 0 && traveledKm / hours > maxTravelKmh) {
|
|
19602
|
-
|
|
19603
|
-
action: actions.impossible_travel,
|
|
19604
|
-
signal: "impossible_travel"
|
|
19605
|
-
});
|
|
19676
|
+
fired.push("impossible_travel");
|
|
19606
19677
|
}
|
|
19607
19678
|
const recentCount = history.filter((attempt) => now - attempt.timestamp <= velocityWindowMs).length;
|
|
19608
|
-
if (recentCount >= velocityMaxAttempts)
|
|
19609
|
-
|
|
19679
|
+
if (recentCount >= velocityMaxAttempts)
|
|
19680
|
+
fired.push("velocity");
|
|
19681
|
+
if (context.isProxy === true)
|
|
19682
|
+
fired.push("proxy");
|
|
19683
|
+
if (context.localHour !== undefined && isWithinOffHours(context.localHour, offHours)) {
|
|
19684
|
+
fired.push("off_hours");
|
|
19610
19685
|
}
|
|
19686
|
+
return fired;
|
|
19687
|
+
};
|
|
19688
|
+
var assessRisk = async (config, context) => {
|
|
19689
|
+
const actions = {
|
|
19690
|
+
...DEFAULT_RULE_ACTIONS,
|
|
19691
|
+
...config.rules
|
|
19692
|
+
};
|
|
19693
|
+
const fired = await detectSignals(config, context);
|
|
19694
|
+
const reasons = fired.map((signal) => ({
|
|
19695
|
+
action: actions[signal],
|
|
19696
|
+
signal
|
|
19697
|
+
}));
|
|
19611
19698
|
return { action: mostSevere2(reasons), reasons };
|
|
19612
19699
|
};
|
|
19613
19700
|
var createRiskEngine = (config) => ({
|
|
19614
19701
|
assessRisk: (context) => assessRisk(config, context),
|
|
19615
19702
|
recordAttempt: (context) => recordLoginAttempt(config, context),
|
|
19703
|
+
scoreRisk: (context, options) => scoreRisk({ ...config, ...options }, context),
|
|
19616
19704
|
trustDevice: (userId, deviceId, label) => trustDevice(config, userId, deviceId, label)
|
|
19617
19705
|
});
|
|
19618
19706
|
var recordLoginAttempt = async (config, context) => {
|
|
@@ -19638,6 +19726,22 @@ var recordLoginAttempt = async (config, context) => {
|
|
|
19638
19726
|
userId: context.userId
|
|
19639
19727
|
});
|
|
19640
19728
|
};
|
|
19729
|
+
var scoreRisk = async (config, context) => {
|
|
19730
|
+
const weights = {
|
|
19731
|
+
...DEFAULT_RISK_WEIGHTS,
|
|
19732
|
+
...config.weights
|
|
19733
|
+
};
|
|
19734
|
+
const defaultThresholds = {
|
|
19735
|
+
deny: DEFAULT_DENY_SCORE,
|
|
19736
|
+
stepUp: DEFAULT_STEP_UP_SCORE
|
|
19737
|
+
};
|
|
19738
|
+
const thresholds = config.thresholds ?? defaultThresholds;
|
|
19739
|
+
const fired = await detectSignals(config, context);
|
|
19740
|
+
const score = fired.reduce((sum, signal) => sum + weights[signal], 0);
|
|
19741
|
+
const action = actionForScore(score, thresholds);
|
|
19742
|
+
const reasons = fired.map((signal) => ({ action, signal }));
|
|
19743
|
+
return { action, reasons, score };
|
|
19744
|
+
};
|
|
19641
19745
|
var trustDevice = async (config, userId, deviceId, label) => {
|
|
19642
19746
|
const now = Date.now();
|
|
19643
19747
|
const existing = await config.knownDeviceStore.findDevice(userId, deviceId);
|
|
@@ -19650,6 +19754,9 @@ var trustDevice = async (config, userId, deviceId, label) => {
|
|
|
19650
19754
|
userId
|
|
19651
19755
|
});
|
|
19652
19756
|
};
|
|
19757
|
+
// src/adaptive/fingerprint.ts
|
|
19758
|
+
var canonical = (signals) => JSON.stringify(signals, (_key, value) => value === null || typeof value !== "object" || Array.isArray(value) ? value : Object.fromEntries(Object.entries(value).sort((left, right) => left[0].localeCompare(right[0]))));
|
|
19759
|
+
var fingerprintDevice = (signals) => hashToken(canonical(signals));
|
|
19653
19760
|
// src/adaptive/inMemoryStores.ts
|
|
19654
19761
|
var deviceKey = (userId, deviceId) => `${userId}:${deviceId}`;
|
|
19655
19762
|
var createInMemoryKnownDeviceStore = () => {
|
|
@@ -19849,16 +19956,95 @@ var expandRule = async (config, resourceType, resourceId, relation, rule, depth,
|
|
|
19849
19956
|
var createFgaEngine = (config) => ({
|
|
19850
19957
|
check: (query) => check(config, query),
|
|
19851
19958
|
deleteWarrant: (warrant) => deleteWarrant(config, warrant),
|
|
19959
|
+
listObjects: (query) => listObjects(config, query),
|
|
19852
19960
|
listSubjects: (query) => listSubjects(config, query),
|
|
19853
19961
|
writeWarrant: (warrant) => writeWarrant(config, warrant)
|
|
19854
19962
|
});
|
|
19855
19963
|
var deleteWarrant = (config, warrant) => config.warrantStore.deleteWarrant(warrant);
|
|
19964
|
+
var listObjects = async (config, query) => {
|
|
19965
|
+
const candidates = await config.warrantStore.listResourceIds(query.resourceType);
|
|
19966
|
+
const allowed = await Promise.all(candidates.map((resourceId) => check(config, {
|
|
19967
|
+
relation: query.relation,
|
|
19968
|
+
resourceId,
|
|
19969
|
+
resourceType: query.resourceType,
|
|
19970
|
+
subjectId: query.subjectId,
|
|
19971
|
+
subjectType: query.subjectType
|
|
19972
|
+
})));
|
|
19973
|
+
return candidates.filter((_resourceId, index) => allowed[index] === true);
|
|
19974
|
+
};
|
|
19856
19975
|
var listSubjects = async (config, query) => {
|
|
19857
19976
|
const found = new Map;
|
|
19858
19977
|
await expand(config, query.resourceType, query.resourceId, query.relation, config.maxDepth ?? DEFAULT_MAX_DEPTH, found, new Set);
|
|
19859
19978
|
return [...found.values()];
|
|
19860
19979
|
};
|
|
19861
19980
|
var writeWarrant = (config, warrant) => config.warrantStore.saveWarrant(warrant);
|
|
19981
|
+
// src/fga/schema.ts
|
|
19982
|
+
var TYPE_PATTERN = /^type\s+(\w+)$/u;
|
|
19983
|
+
var DEFINE_PATTERN = /^define\s+(\w+)\s*:\s*(.+)$/u;
|
|
19984
|
+
var FROM_PATTERN = /^(\w+)\s+from\s+(\w+)$/u;
|
|
19985
|
+
var OR_SEPARATOR = /\s+or\s+/u;
|
|
19986
|
+
var parseTerm = (term) => {
|
|
19987
|
+
const trimmed = term.trim();
|
|
19988
|
+
if (trimmed.startsWith("[")) {
|
|
19989
|
+
const rule2 = { kind: "self" };
|
|
19990
|
+
return rule2;
|
|
19991
|
+
}
|
|
19992
|
+
const fromMatch = FROM_PATTERN.exec(trimmed);
|
|
19993
|
+
if (fromMatch) {
|
|
19994
|
+
const [, relation, viaRelation] = fromMatch;
|
|
19995
|
+
const rule2 = {
|
|
19996
|
+
kind: "tupleToUserset",
|
|
19997
|
+
relation: relation ?? "",
|
|
19998
|
+
viaRelation: viaRelation ?? ""
|
|
19999
|
+
};
|
|
20000
|
+
return rule2;
|
|
20001
|
+
}
|
|
20002
|
+
const rule = { kind: "computedUserset", relation: trimmed };
|
|
20003
|
+
return rule;
|
|
20004
|
+
};
|
|
20005
|
+
var parseExpression = (expression) => {
|
|
20006
|
+
const terms = expression.split(OR_SEPARATOR).map(parseTerm);
|
|
20007
|
+
const [first] = terms;
|
|
20008
|
+
if (terms.length === 1 && first)
|
|
20009
|
+
return first;
|
|
20010
|
+
const rule = { kind: "union", rules: terms };
|
|
20011
|
+
return rule;
|
|
20012
|
+
};
|
|
20013
|
+
var applyType = (schema, line2) => {
|
|
20014
|
+
const match = TYPE_PATTERN.exec(line2);
|
|
20015
|
+
if (!match)
|
|
20016
|
+
return;
|
|
20017
|
+
const [, name] = match;
|
|
20018
|
+
if (name)
|
|
20019
|
+
schema[name] = {};
|
|
20020
|
+
return name;
|
|
20021
|
+
};
|
|
20022
|
+
var applyDefine = (target, line2) => {
|
|
20023
|
+
if (!target)
|
|
20024
|
+
return;
|
|
20025
|
+
const match = DEFINE_PATTERN.exec(line2);
|
|
20026
|
+
if (!match)
|
|
20027
|
+
return;
|
|
20028
|
+
const [, relation, expression] = match;
|
|
20029
|
+
if (relation && expression)
|
|
20030
|
+
target[relation] = parseExpression(expression);
|
|
20031
|
+
};
|
|
20032
|
+
var parseSchema = (dsl) => {
|
|
20033
|
+
const schema = {};
|
|
20034
|
+
let currentType;
|
|
20035
|
+
for (const rawLine of dsl.split(`
|
|
20036
|
+
`)) {
|
|
20037
|
+
const line2 = rawLine.trim();
|
|
20038
|
+
if (line2 === "" || line2.startsWith("#") || line2 === "relations")
|
|
20039
|
+
continue;
|
|
20040
|
+
const typeName = applyType(schema, line2);
|
|
20041
|
+
currentType = typeName ?? currentType;
|
|
20042
|
+
if (typeName !== undefined)
|
|
20043
|
+
continue;
|
|
20044
|
+
applyDefine(currentType ? schema[currentType] : undefined, line2);
|
|
20045
|
+
}
|
|
20046
|
+
return schema;
|
|
20047
|
+
};
|
|
19862
20048
|
// src/fga/inMemoryStores.ts
|
|
19863
20049
|
var createInMemoryWarrantStore = () => {
|
|
19864
20050
|
const warrants = new Map;
|
|
@@ -19867,6 +20053,9 @@ var createInMemoryWarrantStore = () => {
|
|
|
19867
20053
|
warrants.delete(warrantKey(warrant));
|
|
19868
20054
|
},
|
|
19869
20055
|
listForResource: async (resourceType, resourceId, relation) => [...warrants.values()].filter((warrant) => warrant.resourceType === resourceType && warrant.resourceId === resourceId && warrant.relation === relation),
|
|
20056
|
+
listResourceIds: async (resourceType) => [
|
|
20057
|
+
...new Set([...warrants.values()].filter((warrant) => warrant.resourceType === resourceType).map((warrant) => warrant.resourceId))
|
|
20058
|
+
],
|
|
19870
20059
|
saveWarrant: async (warrant) => {
|
|
19871
20060
|
warrants.set(warrantKey(warrant), { ...warrant });
|
|
19872
20061
|
}
|
|
@@ -19901,6 +20090,10 @@ var createPostgresWarrantStore = (db) => ({
|
|
|
19901
20090
|
const rows = await db.select().from(warrantsTable).where(and(eq(warrantsTable.resource_type, resourceType), eq(warrantsTable.resource_id, resourceId), eq(warrantsTable.relation, relation)));
|
|
19902
20091
|
return rows.map(toWarrant);
|
|
19903
20092
|
},
|
|
20093
|
+
listResourceIds: async (resourceType) => {
|
|
20094
|
+
const rows = await db.selectDistinct({ resourceId: warrantsTable.resource_id }).from(warrantsTable).where(eq(warrantsTable.resource_type, resourceType));
|
|
20095
|
+
return rows.map((row) => row.resourceId);
|
|
20096
|
+
},
|
|
19904
20097
|
saveWarrant: async (warrant) => {
|
|
19905
20098
|
await db.insert(warrantsTable).values({
|
|
19906
20099
|
id: warrantKey(warrant),
|
|
@@ -20728,6 +20921,7 @@ export {
|
|
|
20728
20921
|
sessionStore,
|
|
20729
20922
|
sessionRoutes,
|
|
20730
20923
|
sessionCleanup,
|
|
20924
|
+
scoreRisk,
|
|
20731
20925
|
scopeRequiredProviderOptions,
|
|
20732
20926
|
scimTokensTable,
|
|
20733
20927
|
scimRoutes,
|
|
@@ -20758,6 +20952,7 @@ export {
|
|
|
20758
20952
|
pkceProviderOptions,
|
|
20759
20953
|
passwordlessTokensTable,
|
|
20760
20954
|
passwordlessRoutes,
|
|
20955
|
+
parseSchema,
|
|
20761
20956
|
organizationsTable,
|
|
20762
20957
|
organizationRoutes,
|
|
20763
20958
|
organizationMembershipsTable,
|
|
@@ -20779,6 +20974,7 @@ export {
|
|
|
20779
20974
|
listUserOrganizations,
|
|
20780
20975
|
listSubjects,
|
|
20781
20976
|
listRingSessions,
|
|
20977
|
+
listObjects,
|
|
20782
20978
|
knownDevicesTable,
|
|
20783
20979
|
jwkThumbprint,
|
|
20784
20980
|
issueTokenSet,
|
|
@@ -20812,7 +21008,9 @@ export {
|
|
|
20812
21008
|
generateSecureToken,
|
|
20813
21009
|
generateEncryptionKey,
|
|
20814
21010
|
generateBackupCodes,
|
|
21011
|
+
fingerprintDevice,
|
|
20815
21012
|
extractPropFromIdentity,
|
|
21013
|
+
exportAuditCsv,
|
|
20816
21014
|
exchangeToken,
|
|
20817
21015
|
exchangeClientCredentials,
|
|
20818
21016
|
evaluatePassword,
|
|
@@ -20976,5 +21174,5 @@ export {
|
|
|
20976
21174
|
AuthIdentityConflictError
|
|
20977
21175
|
};
|
|
20978
21176
|
|
|
20979
|
-
//# debugId=
|
|
21177
|
+
//# debugId=22C6F88FE2A9D7F764756E2164756E21
|
|
20980
21178
|
//# sourceMappingURL=index.js.map
|