You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 52d69c3
Browse filesBrowse the repository at this point in the historyBrowse files
`Session of *${username}* is used in two browsers and was ended`,
255
+
`Signed in: ${session.device?.browser} on ${session.device?.os}, IP: ${session.ip}, country: ${session.country}`,
256
+
`Other request: ${request.device?.browser} on ${request.device?.os}, IP: ${request.ip}, country: ${request.country}`,
257
+
].join('\n'),
258
+
}),
259
+
});
260
+
},
261
+
})
262
+
```
263
+
264
+
How a copy is found:
265
+
266
+
- On sign-in the browser gets a `session_nonce` cookie next to the auth cookie, its value is stored with the session.
267
+
- Every 5 minutes (`nonceRotationMinutes`) a response replaces the value. A copy in another browser keeps the old one, so the old value
268
+
coming after the new one was used tells that two browsers hold the session. It works the same way when the copy
269
+
gets the new value first.
270
+
- The session is revoked before the callback and both browsers are signed out, as the plugin can't tell which one
271
+
belongs to the user. `request` is the one with the old value, `session` is how the session was signed in.
272
+
- The old value is still accepted until the browser comes with the new one and for a minute after that, so parallel
273
+
requests and a lost response don't end the session.
274
+
- It costs no extra reads: the value is checked against the session which is read on every request anyway, and a
275
+
new value is written together with `last_used_at`.
276
+
- A copy is found only when both browsers use the session. While the computer of the user is off, the copy works
277
+
until the user comes back or the auth token expires, a shorter session lifetime makes that time shorter.
278
+
- Only sessions signed in while the option is set are checked, so after installing it into a running app every
279
+
session is checked from its next sign-in.
280
+
281
+
The callback is not awaited, and its errors are only logged.
282
+
239
283
## Login history
240
284
241
285
Pass `loginHistory` to store every sign-in and every failed password attempt of a user in a resource.
@@ -389,6 +433,8 @@ In the list the country name is shown in a tooltip of the flag, on the show page
389
433
| `canSeeIps` | — | `(adminUser) =>Promise<boolean>`, decides who sees IPs of sessions, see [Hiding IP addresses](#hiding-ip-addresses) |
390
434
| `lastUsedThrottleSeconds` | `60` | How often `last_used_at` is refreshed. Every refresh is one write to the adapter |
391
435
| `onNewLogin` | — | `({ pk, username, session, reasons, revokePath }) =>Promise<void>`, called after a sign-in from a new device or country, see [Notifying about sign-ins from new devices](#notifying-about-sign-ins-from-new-devices) |
436
+
| `onSessionCopied` | — | `({ pk, username, session, request }) =>Promise<void>`, called after a session used in two browsers was revoked, see [Detecting copied sessions](#detecting-copied-sessions) |
437
+
| `nonceRotationMinutes` | `5` | How often the nonce cookie of `onSessionCopied` is replaced. Every replacement is two writes to the adapter |
392
438
| `loginHistory.resourceId` | — | Resource where sign-ins and failed attempts are stored, see [Login history](#login-history) |
393
439
| `loginHistory.resourceColumns` | required with `loginHistory` | Names of the columns of that resource |
394
440
| `loginHistory.failedLogsInterval` | `1m` | How often counted failed attempts are written, one record per user |
0 commit comments