@seamless-auth/types 0.27.0 → 0.28.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @seamless-auth/types
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e2d2a28: Time ranges for the dashboard metrics and security anomalies (fells-code/seamless-auth-api#132).
|
|
8
|
+
|
|
9
|
+
- `DashboardMetricsQuerySchema` (`from`, `to`) and `SecurityAnomaliesQuerySchema` (`from`, `to`, `limit` 1 to 200 with a default of 200, `offset` with a default of 0). The range rules are the same as `MetricsQuerySchema`'s, which now shares them.
|
|
10
|
+
- `DashboardMetricsResponseSchema` gains optional `window`, `newUsers`, `loginSuccess`, `loginFailed`, `successRate`, `otpUsage` and `passkeyUsage`, which cover the requested window. The `*24h` fields keep meaning the last 24 hours.
|
|
11
|
+
- `SecurityAnomaliesResponseSchema` gains optional `window`, `limit` and `offset`, and `total` is documented as every matching event in the window.
|
|
12
|
+
|
|
13
|
+
All the new response fields are optional, so a response from a server without ranges still parses.
|
|
14
|
+
|
|
3
15
|
## 0.27.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -68,6 +68,25 @@ export declare const PartialAuthEventSchema: z.ZodObject<{
|
|
|
68
68
|
updated_at: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
69
69
|
}, z.core.$strip>;
|
|
70
70
|
export type PartialAuthEvent = z.infer<typeof PartialAuthEventSchema>;
|
|
71
|
+
/** The window a ranged response covers, as resolved by the server. */
|
|
72
|
+
export declare const MetricsWindowSchema: z.ZodObject<{
|
|
73
|
+
from: z.ZodString;
|
|
74
|
+
to: z.ZodString;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
export type MetricsWindow = z.infer<typeof MetricsWindowSchema>;
|
|
77
|
+
/** `from`/`to` default to the last 24 hours, as the endpoints did before they took a range. */
|
|
78
|
+
export declare const DashboardMetricsQuerySchema: z.ZodObject<{
|
|
79
|
+
from: z.ZodOptional<z.ZodString>;
|
|
80
|
+
to: z.ZodOptional<z.ZodString>;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
export type DashboardMetricsQuery = z.infer<typeof DashboardMetricsQuerySchema>;
|
|
83
|
+
export declare const SecurityAnomaliesQuerySchema: z.ZodObject<{
|
|
84
|
+
from: z.ZodOptional<z.ZodString>;
|
|
85
|
+
to: z.ZodOptional<z.ZodString>;
|
|
86
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
87
|
+
offset: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
export type SecurityAnomaliesQuery = z.infer<typeof SecurityAnomaliesQuerySchema>;
|
|
71
90
|
export declare const SecurityAnomaliesResponseSchema: z.ZodObject<{
|
|
72
91
|
suspiciousEvents: z.ZodArray<z.ZodObject<{
|
|
73
92
|
id: z.ZodOptional<z.ZodString>;
|
|
@@ -87,6 +106,12 @@ export declare const SecurityAnomaliesResponseSchema: z.ZodObject<{
|
|
|
87
106
|
updated_at: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
88
107
|
}, z.core.$strip>>;
|
|
89
108
|
total: z.ZodNumber;
|
|
109
|
+
window: z.ZodOptional<z.ZodObject<{
|
|
110
|
+
from: z.ZodString;
|
|
111
|
+
to: z.ZodString;
|
|
112
|
+
}, z.core.$strip>>;
|
|
113
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
114
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
90
115
|
}, z.core.$strip>;
|
|
91
116
|
export type SecurityAnomaliesResponse = z.infer<typeof SecurityAnomaliesResponseSchema>;
|
|
92
117
|
export declare const DashboardMetricsResponseSchema: z.ZodObject<{
|
|
@@ -99,6 +124,16 @@ export declare const DashboardMetricsResponseSchema: z.ZodObject<{
|
|
|
99
124
|
otpUsage24h: z.ZodNumber;
|
|
100
125
|
passkeyUsage24h: z.ZodNumber;
|
|
101
126
|
databaseSize: z.ZodNumber;
|
|
127
|
+
window: z.ZodOptional<z.ZodObject<{
|
|
128
|
+
from: z.ZodString;
|
|
129
|
+
to: z.ZodString;
|
|
130
|
+
}, z.core.$strip>>;
|
|
131
|
+
newUsers: z.ZodOptional<z.ZodNumber>;
|
|
132
|
+
loginSuccess: z.ZodOptional<z.ZodNumber>;
|
|
133
|
+
loginFailed: z.ZodOptional<z.ZodNumber>;
|
|
134
|
+
successRate: z.ZodOptional<z.ZodNumber>;
|
|
135
|
+
otpUsage: z.ZodOptional<z.ZodNumber>;
|
|
136
|
+
passkeyUsage: z.ZodOptional<z.ZodNumber>;
|
|
102
137
|
}, z.core.$strip>;
|
|
103
138
|
export type DashboardMetricsResponse = z.infer<typeof DashboardMetricsResponseSchema>;
|
|
104
139
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/schemas/metrics/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,qBAAqB;;;EAA0B,CAAC;AAE7D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/schemas/metrics/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,qBAAqB;;;EAA0B,CAAC;AAE7D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAiDpE,eAAO,MAAM,kBAAkB;;;;;;;;iBAO0B,CAAC;AAE1D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,0BAA0B;;;iBAGrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,8BAA8B;;;;;iBAEzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF,eAAO,MAAM,8BAA8B;;;;iBAIzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF,eAAO,MAAM,iCAAiC;;;;;;iBAE5C,CAAC;AAEH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAE5F,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;iBAA4B,CAAC;AAEhE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,sEAAsE;AACtE,eAAO,MAAM,mBAAmB;;;iBAAiD,CAAC;AAElF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,+FAA+F;AAC/F,eAAO,MAAM,2BAA2B;;;iBAKiB,CAAC;AAE1D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,4BAA4B;;;;;iBAOgB,CAAC;AAE1D,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;iBAU1C,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;iBAsBzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC"}
|
|
@@ -2,40 +2,44 @@ import { z } from 'zod';
|
|
|
2
2
|
import { AuthEventSchema } from '../authEvent/schema.js';
|
|
3
3
|
const MAX_METRICS_WINDOW_MS = 1000 * 60 * 60 * 24 * 366; // ~1 year
|
|
4
4
|
export const MetricsIntervalSchema = z.enum(['hour', 'day']);
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
userId: z.string().optional(),
|
|
8
|
-
from: z.string().optional(),
|
|
9
|
-
to: z.string().optional(),
|
|
10
|
-
interval: MetricsIntervalSchema.optional().default('hour'),
|
|
11
|
-
})
|
|
12
|
-
.superRefine((data, ctx) => {
|
|
5
|
+
// Shared by every metrics query that takes a range, so they agree on what a valid window is.
|
|
6
|
+
function timeRangeIssues(data) {
|
|
13
7
|
const fromDate = data.from ? new Date(data.from) : undefined;
|
|
14
8
|
const toDate = data.to ? new Date(data.to) : undefined;
|
|
15
9
|
const fromValid = fromDate !== undefined && !Number.isNaN(fromDate.getTime());
|
|
16
10
|
const toValid = toDate !== undefined && !Number.isNaN(toDate.getTime());
|
|
11
|
+
const issues = [];
|
|
17
12
|
if (data.from !== undefined && !fromValid) {
|
|
18
|
-
|
|
13
|
+
issues.push({ path: ['from'], message: 'Invalid from date' });
|
|
19
14
|
}
|
|
20
15
|
if (data.to !== undefined && !toValid) {
|
|
21
|
-
|
|
16
|
+
issues.push({ path: ['to'], message: 'Invalid to date' });
|
|
22
17
|
}
|
|
23
18
|
if (!fromValid || !toValid || !fromDate || !toDate) {
|
|
24
|
-
return;
|
|
19
|
+
return issues;
|
|
25
20
|
}
|
|
26
21
|
if (fromDate.getTime() > toDate.getTime()) {
|
|
27
|
-
|
|
28
|
-
return;
|
|
22
|
+
return [{ path: ['to'], message: 'from must be on or before to' }];
|
|
29
23
|
}
|
|
30
24
|
// Unbounded windows let a single request scan the whole event table.
|
|
31
25
|
if (toDate.getTime() - fromDate.getTime() > MAX_METRICS_WINDOW_MS) {
|
|
32
|
-
|
|
33
|
-
code: 'custom',
|
|
34
|
-
path: ['to'],
|
|
35
|
-
message: 'time range exceeds the maximum window',
|
|
36
|
-
});
|
|
26
|
+
return [{ path: ['to'], message: 'time range exceeds the maximum window' }];
|
|
37
27
|
}
|
|
38
|
-
|
|
28
|
+
return issues;
|
|
29
|
+
}
|
|
30
|
+
function refineTimeRange(data, ctx) {
|
|
31
|
+
for (const issue of timeRangeIssues(data)) {
|
|
32
|
+
ctx.addIssue({ code: 'custom', ...issue });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export const MetricsQuerySchema = z
|
|
36
|
+
.object({
|
|
37
|
+
userId: z.string().optional(),
|
|
38
|
+
from: z.string().optional(),
|
|
39
|
+
to: z.string().optional(),
|
|
40
|
+
interval: MetricsIntervalSchema.optional().default('hour'),
|
|
41
|
+
})
|
|
42
|
+
.superRefine((data, ctx) => refineTimeRange(data, ctx));
|
|
39
43
|
export const AuthEventSummaryItemSchema = z.object({
|
|
40
44
|
type: z.string(),
|
|
41
45
|
count: z.number(),
|
|
@@ -61,9 +65,33 @@ export const LoginStatsResponseSchema = z.object({
|
|
|
61
65
|
* event is still worth surfacing, so every field is optional here.
|
|
62
66
|
*/
|
|
63
67
|
export const PartialAuthEventSchema = AuthEventSchema.partial();
|
|
68
|
+
/** The window a ranged response covers, as resolved by the server. */
|
|
69
|
+
export const MetricsWindowSchema = z.object({ from: z.string(), to: z.string() });
|
|
70
|
+
/** `from`/`to` default to the last 24 hours, as the endpoints did before they took a range. */
|
|
71
|
+
export const DashboardMetricsQuerySchema = z
|
|
72
|
+
.object({
|
|
73
|
+
from: z.string().optional(),
|
|
74
|
+
to: z.string().optional(),
|
|
75
|
+
})
|
|
76
|
+
.superRefine((data, ctx) => refineTimeRange(data, ctx));
|
|
77
|
+
export const SecurityAnomaliesQuerySchema = z
|
|
78
|
+
.object({
|
|
79
|
+
from: z.string().optional(),
|
|
80
|
+
to: z.string().optional(),
|
|
81
|
+
limit: z.coerce.number().int().min(1).max(200).optional().default(200),
|
|
82
|
+
offset: z.coerce.number().int().min(0).optional().default(0),
|
|
83
|
+
})
|
|
84
|
+
.superRefine((data, ctx) => refineTimeRange(data, ctx));
|
|
64
85
|
export const SecurityAnomaliesResponseSchema = z.object({
|
|
65
86
|
suspiciousEvents: z.array(PartialAuthEventSchema),
|
|
87
|
+
/**
|
|
88
|
+
* Every matching event in the window. Servers before the range was added reported
|
|
89
|
+
* the number returned, at most 200, here instead.
|
|
90
|
+
*/
|
|
66
91
|
total: z.number().int().nonnegative(),
|
|
92
|
+
window: MetricsWindowSchema.optional(),
|
|
93
|
+
limit: z.number().int().optional(),
|
|
94
|
+
offset: z.number().int().optional(),
|
|
67
95
|
});
|
|
68
96
|
export const DashboardMetricsResponseSchema = z.object({
|
|
69
97
|
totalUsers: z.number(),
|
|
@@ -75,5 +103,17 @@ export const DashboardMetricsResponseSchema = z.object({
|
|
|
75
103
|
otpUsage24h: z.number(),
|
|
76
104
|
passkeyUsage24h: z.number(),
|
|
77
105
|
databaseSize: z.number(),
|
|
106
|
+
/**
|
|
107
|
+
* The same figures as the `*24h` fields, over the requested window instead of the
|
|
108
|
+
* last 24 hours. The `*24h` fields keep their meaning whatever window is asked for.
|
|
109
|
+
* Optional because servers before the range was added do not send them.
|
|
110
|
+
*/
|
|
111
|
+
window: MetricsWindowSchema.optional(),
|
|
112
|
+
newUsers: z.number().optional(),
|
|
113
|
+
loginSuccess: z.number().optional(),
|
|
114
|
+
loginFailed: z.number().optional(),
|
|
115
|
+
successRate: z.number().optional(),
|
|
116
|
+
otpUsage: z.number().optional(),
|
|
117
|
+
passkeyUsage: z.number().optional(),
|
|
78
118
|
});
|
|
79
119
|
//# sourceMappingURL=schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/schemas/metrics/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC,UAAU;AAEnE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/schemas/metrics/schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -7,6 +7,53 @@ export const MetricsIntervalSchema = z.enum(['hour', 'day']);
|
|
|
7
7
|
|
|
8
8
|
export type MetricsInterval = z.infer<typeof MetricsIntervalSchema>;
|
|
9
9
|
|
|
10
|
+
type RangeIssue = { path: ['from'] | ['to']; message: string };
|
|
11
|
+
|
|
12
|
+
// Shared by every metrics query that takes a range, so they agree on what a valid window is.
|
|
13
|
+
function timeRangeIssues(data: {
|
|
14
|
+
from?: string | undefined;
|
|
15
|
+
to?: string | undefined;
|
|
16
|
+
}): RangeIssue[] {
|
|
17
|
+
const fromDate = data.from ? new Date(data.from) : undefined;
|
|
18
|
+
const toDate = data.to ? new Date(data.to) : undefined;
|
|
19
|
+
|
|
20
|
+
const fromValid = fromDate !== undefined && !Number.isNaN(fromDate.getTime());
|
|
21
|
+
const toValid = toDate !== undefined && !Number.isNaN(toDate.getTime());
|
|
22
|
+
const issues: RangeIssue[] = [];
|
|
23
|
+
|
|
24
|
+
if (data.from !== undefined && !fromValid) {
|
|
25
|
+
issues.push({ path: ['from'], message: 'Invalid from date' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (data.to !== undefined && !toValid) {
|
|
29
|
+
issues.push({ path: ['to'], message: 'Invalid to date' });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!fromValid || !toValid || !fromDate || !toDate) {
|
|
33
|
+
return issues;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (fromDate.getTime() > toDate.getTime()) {
|
|
37
|
+
return [{ path: ['to'], message: 'from must be on or before to' }];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Unbounded windows let a single request scan the whole event table.
|
|
41
|
+
if (toDate.getTime() - fromDate.getTime() > MAX_METRICS_WINDOW_MS) {
|
|
42
|
+
return [{ path: ['to'], message: 'time range exceeds the maximum window' }];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return issues;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function refineTimeRange<T extends { from?: string | undefined; to?: string | undefined }>(
|
|
49
|
+
data: T,
|
|
50
|
+
ctx: z.RefinementCtx<T>,
|
|
51
|
+
) {
|
|
52
|
+
for (const issue of timeRangeIssues(data)) {
|
|
53
|
+
ctx.addIssue({ code: 'custom', ...issue });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
10
57
|
export const MetricsQuerySchema = z
|
|
11
58
|
.object({
|
|
12
59
|
userId: z.string().optional(),
|
|
@@ -14,39 +61,7 @@ export const MetricsQuerySchema = z
|
|
|
14
61
|
to: z.string().optional(),
|
|
15
62
|
interval: MetricsIntervalSchema.optional().default('hour'),
|
|
16
63
|
})
|
|
17
|
-
.superRefine((data, ctx) =>
|
|
18
|
-
const fromDate = data.from ? new Date(data.from) : undefined;
|
|
19
|
-
const toDate = data.to ? new Date(data.to) : undefined;
|
|
20
|
-
|
|
21
|
-
const fromValid = fromDate !== undefined && !Number.isNaN(fromDate.getTime());
|
|
22
|
-
const toValid = toDate !== undefined && !Number.isNaN(toDate.getTime());
|
|
23
|
-
|
|
24
|
-
if (data.from !== undefined && !fromValid) {
|
|
25
|
-
ctx.addIssue({ code: 'custom', path: ['from'], message: 'Invalid from date' });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (data.to !== undefined && !toValid) {
|
|
29
|
-
ctx.addIssue({ code: 'custom', path: ['to'], message: 'Invalid to date' });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!fromValid || !toValid || !fromDate || !toDate) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (fromDate.getTime() > toDate.getTime()) {
|
|
37
|
-
ctx.addIssue({ code: 'custom', path: ['to'], message: 'from must be on or before to' });
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Unbounded windows let a single request scan the whole event table.
|
|
42
|
-
if (toDate.getTime() - fromDate.getTime() > MAX_METRICS_WINDOW_MS) {
|
|
43
|
-
ctx.addIssue({
|
|
44
|
-
code: 'custom',
|
|
45
|
-
path: ['to'],
|
|
46
|
-
message: 'time range exceeds the maximum window',
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
});
|
|
64
|
+
.superRefine((data, ctx) => refineTimeRange(data, ctx));
|
|
50
65
|
|
|
51
66
|
export type MetricsQuery = z.infer<typeof MetricsQuerySchema>;
|
|
52
67
|
|
|
@@ -93,9 +108,42 @@ export const PartialAuthEventSchema = AuthEventSchema.partial();
|
|
|
93
108
|
|
|
94
109
|
export type PartialAuthEvent = z.infer<typeof PartialAuthEventSchema>;
|
|
95
110
|
|
|
111
|
+
/** The window a ranged response covers, as resolved by the server. */
|
|
112
|
+
export const MetricsWindowSchema = z.object({ from: z.string(), to: z.string() });
|
|
113
|
+
|
|
114
|
+
export type MetricsWindow = z.infer<typeof MetricsWindowSchema>;
|
|
115
|
+
|
|
116
|
+
/** `from`/`to` default to the last 24 hours, as the endpoints did before they took a range. */
|
|
117
|
+
export const DashboardMetricsQuerySchema = z
|
|
118
|
+
.object({
|
|
119
|
+
from: z.string().optional(),
|
|
120
|
+
to: z.string().optional(),
|
|
121
|
+
})
|
|
122
|
+
.superRefine((data, ctx) => refineTimeRange(data, ctx));
|
|
123
|
+
|
|
124
|
+
export type DashboardMetricsQuery = z.infer<typeof DashboardMetricsQuerySchema>;
|
|
125
|
+
|
|
126
|
+
export const SecurityAnomaliesQuerySchema = z
|
|
127
|
+
.object({
|
|
128
|
+
from: z.string().optional(),
|
|
129
|
+
to: z.string().optional(),
|
|
130
|
+
limit: z.coerce.number().int().min(1).max(200).optional().default(200),
|
|
131
|
+
offset: z.coerce.number().int().min(0).optional().default(0),
|
|
132
|
+
})
|
|
133
|
+
.superRefine((data, ctx) => refineTimeRange(data, ctx));
|
|
134
|
+
|
|
135
|
+
export type SecurityAnomaliesQuery = z.infer<typeof SecurityAnomaliesQuerySchema>;
|
|
136
|
+
|
|
96
137
|
export const SecurityAnomaliesResponseSchema = z.object({
|
|
97
138
|
suspiciousEvents: z.array(PartialAuthEventSchema),
|
|
139
|
+
/**
|
|
140
|
+
* Every matching event in the window. Servers before the range was added reported
|
|
141
|
+
* the number returned, at most 200, here instead.
|
|
142
|
+
*/
|
|
98
143
|
total: z.number().int().nonnegative(),
|
|
144
|
+
window: MetricsWindowSchema.optional(),
|
|
145
|
+
limit: z.number().int().optional(),
|
|
146
|
+
offset: z.number().int().optional(),
|
|
99
147
|
});
|
|
100
148
|
|
|
101
149
|
export type SecurityAnomaliesResponse = z.infer<typeof SecurityAnomaliesResponseSchema>;
|
|
@@ -110,6 +158,18 @@ export const DashboardMetricsResponseSchema = z.object({
|
|
|
110
158
|
otpUsage24h: z.number(),
|
|
111
159
|
passkeyUsage24h: z.number(),
|
|
112
160
|
databaseSize: z.number(),
|
|
161
|
+
/**
|
|
162
|
+
* The same figures as the `*24h` fields, over the requested window instead of the
|
|
163
|
+
* last 24 hours. The `*24h` fields keep their meaning whatever window is asked for.
|
|
164
|
+
* Optional because servers before the range was added do not send them.
|
|
165
|
+
*/
|
|
166
|
+
window: MetricsWindowSchema.optional(),
|
|
167
|
+
newUsers: z.number().optional(),
|
|
168
|
+
loginSuccess: z.number().optional(),
|
|
169
|
+
loginFailed: z.number().optional(),
|
|
170
|
+
successRate: z.number().optional(),
|
|
171
|
+
otpUsage: z.number().optional(),
|
|
172
|
+
passkeyUsage: z.number().optional(),
|
|
113
173
|
});
|
|
114
174
|
|
|
115
175
|
export type DashboardMetricsResponse = z.infer<typeof DashboardMetricsResponseSchema>;
|