Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/ranged-internal-metrics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'seamless-auth-api': minor
---

`GET /internal/metrics/dashboard` and `GET /internal/security/anomalies` accept `from` and `to` (#132), with the same validation as the `/internal/auth-events/*` endpoints and a default of the last 24 hours. Both responses carry the `window` they covered.

- Dashboard metrics adds `newUsers`, `loginSuccess`, `loginFailed`, `successRate`, `otpUsage` and `passkeyUsage` for the requested window. The `*24h` fields keep meaning the last 24 hours.
- Security anomalies takes `limit` (1 to 200, default 200) and `offset`. `total` now counts every match in the window. It used to report the number returned, which was capped at 200, so a caller could not tell there were more.

Requires `@seamless-auth/types` 0.28.0.
13 changes: 13 additions & 0 deletions docs/admin-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,19 @@ endpoint, or the audit export, to find the individual events.
The `/internal/auth-events/*` endpoints all accept the same query parameters: `from`, `to`,
`userId`, and `interval` (`hour` or `day`, timeseries only).

`/internal/metrics/dashboard` and `/internal/security/anomalies` take `from` and `to` as well,
with the same validation, and default to the last 24 hours. Both answer with the `window` they
covered.

- **Dashboard metrics.** The `*24h` fields (`loginSuccess24h`, `successRate24h`, and so on)
always cover the last 24 hours, whatever window is asked for, so a caller never gets a
different period under the same name. The window-neutral fields beside them (`newUsers`,
`loginSuccess`, `loginFailed`, `successRate`, `otpUsage`, `passkeyUsage`) cover the
requested window. `totalUsers`, `activeSessions` and `databaseSize` are current totals.
- **Security anomalies.** Failed and suspicious events in the window, newest first, paged with
`limit` (at most 200, the default) and `offset`. `total` counts every match in the window,
not just the page.

### Date windows

`from` and `to` are parsed as dates and rejected with `400` when unparseable or inverted. The
Expand Down
143 changes: 139 additions & 4 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -6100,14 +6100,37 @@
"/internal/security/anomalies": {
"get": {
"summary": "Detect suspicious activity",
"description": "Failed and suspicious auth events in [from, to), newest first. Defaults to the last 24 hours. `total` counts every match in the window, not just this page.",
"tags": ["Internal"],
"security": [{ "bearerAuth": [] }],
"parameters": [
{ "schema": { "type": "string" }, "required": false, "name": "from", "in": "query" },
{ "schema": { "type": "string" }, "required": false, "name": "to", "in": "query" },
{
"schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 200 },
"required": false,
"name": "limit",
"in": "query"
},
{
"schema": { "type": "integer", "nullable": true, "minimum": 0, "default": 0 },
"required": false,
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "HTTP 200",
"content": {
"application/json": {
"example": { "suspiciousEvents": [null], "total": 0 },
"example": {
"suspiciousEvents": [null],
"total": 0,
"window": { "from": "string", "to": "string" },
"limit": 0,
"offset": 0
},
"schema": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -6146,13 +6169,61 @@
}
}
},
"total": { "type": "integer", "minimum": 0 }
"total": { "type": "integer", "minimum": 0 },
"window": {
"type": "object",
"properties": { "from": { "type": "string" }, "to": { "type": "string" } },
"required": ["from", "to"]
},
"limit": { "type": "integer" },
"offset": { "type": "integer" }
},
"required": ["suspiciousEvents", "total"]
}
}
}
},
"400": {
"description": "HTTP 400",
"content": {
"application/json": {
"example": {
"error": "string",
"message": "string",
"details": { "issues": [null] }
},
"schema": {
"type": "object",
"properties": {
"error": { "type": "string" },
"message": { "type": "string" },
"details": {
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "array",
"items": { "anyOf": [{ "type": "string" }, { "type": "number" }] }
},
"code": { "type": "string" },
"message": { "type": "string" }
},
"required": ["path", "code", "message"]
}
}
},
"required": ["issues"]
}
},
"required": ["error"]
}
}
}
},
"429": {
"description": "HTTP 429",
"content": {
Expand Down Expand Up @@ -6185,8 +6256,13 @@
"/internal/metrics/dashboard": {
"get": {
"summary": "Dashboard metrics",
"description": "Headline figures. The `*24h` fields always cover the last 24 hours. `newUsers`, `loginSuccess`, `loginFailed`, `successRate`, `otpUsage` and `passkeyUsage` cover [from, to), which defaults to the last 24 hours, and `window` says which period that was.",
"tags": ["Internal"],
"security": [{ "bearerAuth": [] }],
"parameters": [
{ "schema": { "type": "string" }, "required": false, "name": "from", "in": "query" },
{ "schema": { "type": "string" }, "required": false, "name": "to", "in": "query" }
],
"responses": {
"200": {
"description": "HTTP 200",
Expand All @@ -6201,7 +6277,14 @@
"successRate24h": 0,
"otpUsage24h": 0,
"passkeyUsage24h": 0,
"databaseSize": 0
"databaseSize": 0,
"window": { "from": "string", "to": "string" },
"newUsers": 0,
"loginSuccess": 0,
"loginFailed": 0,
"successRate": 0,
"otpUsage": 0,
"passkeyUsage": 0
},
"schema": {
"type": "object",
Expand All @@ -6214,7 +6297,18 @@
"successRate24h": { "type": "number" },
"otpUsage24h": { "type": "number" },
"passkeyUsage24h": { "type": "number" },
"databaseSize": { "type": "number" }
"databaseSize": { "type": "number" },
"window": {
"type": "object",
"properties": { "from": { "type": "string" }, "to": { "type": "string" } },
"required": ["from", "to"]
},
"newUsers": { "type": "number" },
"loginSuccess": { "type": "number" },
"loginFailed": { "type": "number" },
"successRate": { "type": "number" },
"otpUsage": { "type": "number" },
"passkeyUsage": { "type": "number" }
},
"required": [
"totalUsers",
Expand All @@ -6231,6 +6325,47 @@
}
}
},
"400": {
"description": "HTTP 400",
"content": {
"application/json": {
"example": {
"error": "string",
"message": "string",
"details": { "issues": [null] }
},
"schema": {
"type": "object",
"properties": {
"error": { "type": "string" },
"message": { "type": "string" },
"details": {
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "array",
"items": { "anyOf": [{ "type": "string" }, { "type": "number" }] }
},
"code": { "type": "string" },
"message": { "type": "string" }
},
"required": ["path", "code", "message"]
}
}
},
"required": ["issues"]
}
},
"required": ["error"]
}
}
}
},
"429": {
"description": "HTTP 429",
"content": {
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@seamless-auth/messaging": "^0.2.0",
"@seamless-auth/messaging-aws": "^0.2.0",
"@seamless-auth/messaging-twilio": "^0.2.0",
"@seamless-auth/types": "^0.27.0",
"@seamless-auth/types": "^0.28.0",
"@simplewebauthn/server": "^14.0.3",
"base64url": "^3.0.1",
"bcrypt-ts": "^9.0.2",
Expand Down
Loading
Loading