Skip to content

Commit 0ba5356

Browse files
committed
fix: Change time-related variables to use milliseconds
1 parent 68fd187 commit 0ba5356

4 files changed

Lines changed: 90 additions & 80 deletions

File tree

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ function dbInit(): sqlite.Database {
5151
db.run(`
5252
CREATE TABLE IF NOT EXISTS session (
5353
id_hash TEXT PRIMARY KEY,
54-
session_exp INTEGER NOT NULL,
55-
token_exp INTEGER NOT NULL,
54+
session_exp_epoch_ms INTEGER NOT NULL,
55+
token_exp_epoch_ms INTEGER NOT NULL,
5656
token_1_hash TEXT NOT NULL,
5757
token_2_hash TEXT
5858
)
@@ -66,8 +66,8 @@ function dbSelectSession(db: sqlite.Database, idHash: string): tcs.SessionData |
6666
return undefined;
6767
}
6868
return {
69-
sessionExp: new Date(row.session_exp),
70-
tokenExp: new Date(row.token_exp),
69+
sessionExpEpochMs: row.session_exp_epoch_ms,
70+
tokenExpEpochMs: row.token_exp_epoch_ms,
7171
token1Hash: row.token_1_hash,
7272
token2Hash: row.token_2_hash,
7373
};
@@ -76,13 +76,13 @@ function dbSelectSession(db: sqlite.Database, idHash: string): tcs.SessionData |
7676
function dbSetSession(db: sqlite.Database, action: tcs.SetSessionAction): void {
7777
db.query(
7878
`
79-
INSERT OR REPLACE INTO session (id_hash, session_exp, token_exp, token_1_hash, token_2_hash)
80-
VALUES (:idHash, :sessionExp, :tokenExp, :token1Hash, :token2Hash)
79+
INSERT OR REPLACE INTO session (id_hash, session_exp_epoch_ms, token_exp_epoch_ms, token_1_hash, token_2_hash)
80+
VALUES (:idHash, :sessionExpEpochMs, :tokenExpEpochMs, :token1Hash, :token2Hash)
8181
`,
8282
).run({
8383
idHash: action.idHash,
84-
sessionExp: action.sessionData.sessionExp.getTime(),
85-
tokenExp: action.sessionData.tokenExp.getTime(),
84+
sessionExpEpochMs: action.sessionData.sessionExpEpochMs,
85+
tokenExpEpochMs: action.sessionData.tokenExpEpochMs,
8686
token1Hash: action.sessionData.token1Hash,
8787
token2Hash: action.sessionData.token2Hash,
8888
});
@@ -208,8 +208,8 @@ You can use custom expiration times by passing configuration options to the func
208208
import * as tcs from "tiny-cookie-session";
209209

210210
const config = {
211-
sessionExpiresIn: 5 * 60 * 60 * 1000, // 5 hours
212-
tokenExpiresIn: 10 * 60 * 1000, // 10 minutes
211+
sessionExpiresInMs: 5 * 60 * 60 * 1000, // 5 hours
212+
tokenExpiresInMs: 10 * 60 * 1000, // 10 minutes
213213
};
214214

215215
tcs.consume({ config /* other params */ });
@@ -219,21 +219,21 @@ tcs.login({ config });
219219

220220
### Session Expiration Time
221221

222-
The `sessionExpiresIn` value controls how long a session can remain active without user interaction,
222+
The `sessionExpiresInMs` value controls how long a session can remain active without user interaction,
223223
often referred to as "log out after X minutes of inactivity."
224224

225-
For example, with `sessionExpiresIn: 30 * 60 * 1000` (30 minutes),
225+
For example, with `sessionExpiresInMs: 30 * 60 * 1000` (30 minutes),
226226
a user can remain logged in indefinitely by making requests at least every 29 minutes.
227227

228228
### Token Expiration Time
229229

230-
The `tokenExpiresIn` value controls how often the token is rotated.
230+
The `tokenExpiresInMs` value controls how often the token is rotated.
231231
When a token expires but the session is still valid, the system generates a new token.
232232

233233
You should set this to a value as short as possible, but still longer than the longest HTTP request
234234
time your users might experience.
235235
For example, if your app might take up to 3 minutes (in a single request) for uploading large files,
236-
you should set `tokenExpiresIn` to 3 minutes.
236+
you should set `tokenExpiresInMs` to 3 minutes.
237237
The only reason we don't rotate the token on every request is to handle a race condition
238238
where the user makes two requests at the same time.
239239

@@ -280,7 +280,7 @@ or to identify attackers from other signals (e.g., IP address, User-Agent, geolo
280280

281281
There are two possible approaches to mitigate this risk:
282282

283-
1. Set a short session expiration time (`sessionExpiresIn`).
283+
1. Set a short session expiration time (`sessionExpiresInMs`).
284284
2. Implement a "Don't remember me" option.
285285

286286
The "Don't remember me" feature can be implemented by removing the `Expires` and `Max-Age`

index.d.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
export type CookieOptions = {
2-
maxAge?: number;
3-
expires?: Date;
4-
domain?: string;
5-
path?: string;
6-
httpOnly?: boolean;
7-
secure?: boolean;
8-
sameSite?: "strict" | "lax" | "none";
2+
readonly maxAge?: number;
3+
readonly expires?: Date;
4+
readonly domain?: string;
5+
readonly path?: string;
6+
readonly httpOnly?: boolean;
7+
readonly secure?: boolean;
8+
readonly sameSite?: "strict" | "lax" | "none";
99
};
1010

1111
export type Cookie = {
12-
value: string;
13-
options: CookieOptions;
12+
readonly value: string;
13+
readonly options: CookieOptions;
1414
};
1515

1616
export type Config = {
17-
readonly dateNow?: () => Date;
18-
readonly sessionExpiresIn?: number;
19-
readonly tokenExpiresIn?: number;
17+
readonly nowEpochMs?: () => number;
18+
readonly sessionExpiresInMs?: number;
19+
readonly tokenExpiresInMs?: number;
2020
};
2121

2222
type Credential = {
@@ -26,10 +26,10 @@ type Credential = {
2626
};
2727

2828
export type SessionData = {
29-
readonly sessionExp: Date;
29+
readonly sessionExpEpochMs: number;
30+
readonly tokenExpEpochMs: number;
3031
readonly token1Hash: string;
3132
readonly token2Hash: string | null;
32-
readonly tokenExp: Date;
3333
};
3434

3535
export type DeleteSessionAction = {

index.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ export async function login(arg) {
5454
const token = generate256BitEntropyHex();
5555
const tokenHash = await hash(token);
5656

57-
const now = arg?.config?.dateNow?.() ?? new Date();
57+
const nowEpochMs = arg?.config?.nowEpochMs?.() ?? Date.now();
5858

59-
const sessionExpiresIn = arg?.config?.sessionExpiresIn ?? defaultSessionExpiresIn;
60-
const sessionExp = new Date(now.getTime() + sessionExpiresIn);
59+
const sessionExpiresInMs = arg?.config?.sessionExpiresInMs ?? defaultSessionExpiresIn;
60+
const sessionExpEpochMs = nowEpochMs + sessionExpiresInMs;
6161

62-
const tokenExpiresIn = arg?.config?.tokenExpiresIn ?? defaultTokenExpiresIn;
63-
const tokenExp = new Date(now.getTime() + tokenExpiresIn);
62+
const tokenExpiresInMs = arg?.config?.tokenExpiresInMs ?? defaultTokenExpiresIn;
63+
const tokenExpEpochMs = nowEpochMs + tokenExpiresInMs;
6464

6565
/** @type {import("./index").Cookie} */
6666
const cookie = {
@@ -69,7 +69,7 @@ export async function login(arg) {
6969
httpOnly: true,
7070
sameSite: "strict",
7171
secure: true,
72-
expires: sessionExp,
72+
expires: new Date(sessionExpEpochMs),
7373
},
7474
};
7575

@@ -82,8 +82,8 @@ export async function login(arg) {
8282
sessionData: {
8383
token1Hash: tokenHash,
8484
token2Hash: null,
85-
sessionExp: sessionExp,
86-
tokenExp: tokenExp,
85+
sessionExpEpochMs,
86+
tokenExpEpochMs,
8787
},
8888
},
8989
};
@@ -141,9 +141,9 @@ export async function consume(arg) {
141141
};
142142
}
143143

144-
const now = arg.config?.dateNow?.() ?? new Date();
144+
const nowEpochMs = arg.config?.nowEpochMs?.() ?? Date.now();
145145

146-
const isSessionExpired = arg.sessionData.sessionExp.getTime() <= now.getTime();
146+
const isSessionExpired = arg.sessionData.sessionExpEpochMs <= nowEpochMs;
147147
if (isSessionExpired) {
148148
return {
149149
state: "Expired",
@@ -162,16 +162,16 @@ export async function consume(arg) {
162162
return { state: "Active" };
163163
}
164164

165-
const isTokenExpired = arg.sessionData.tokenExp.getTime() <= now.getTime();
165+
const isTokenExpired = arg.sessionData.tokenExpEpochMs <= nowEpochMs;
166166
if (isTokenExpired) {
167167
const nextToken = generate256BitEntropyHex();
168168
const nextTokenHash = await hash(nextToken);
169169

170-
const sessionExpiresIn = arg.config?.sessionExpiresIn ?? defaultSessionExpiresIn;
171-
const nextSessionExp = new Date(now.getTime() + sessionExpiresIn);
170+
const sessionExpiresInMs = arg.config?.sessionExpiresInMs ?? defaultSessionExpiresIn;
171+
const nextSessionExpEpochMs = nowEpochMs + sessionExpiresInMs;
172172

173-
const tokenExpiresIn = arg.config?.tokenExpiresIn ?? defaultTokenExpiresIn;
174-
const nextTokenExp = new Date(now.getTime() + tokenExpiresIn);
173+
const tokenExpiresInMs = arg.config?.tokenExpiresInMs ?? defaultTokenExpiresIn;
174+
const nextTokenExpEpochMs = nowEpochMs + tokenExpiresInMs;
175175

176176
/** @type {import("./index").Cookie} */
177177
const cookie = {
@@ -181,7 +181,7 @@ export async function consume(arg) {
181181
sameSite: "strict",
182182
path: "/",
183183
secure: true,
184-
expires: nextSessionExp,
184+
expires: new Date(nextSessionExpEpochMs),
185185
},
186186
};
187187

@@ -195,8 +195,8 @@ export async function consume(arg) {
195195
sessionData: {
196196
token1Hash: nextTokenHash,
197197
token2Hash: arg.sessionData.token1Hash,
198-
sessionExp: nextSessionExp,
199-
tokenExp: nextTokenExp,
198+
sessionExpEpochMs: nextSessionExpEpochMs,
199+
tokenExpEpochMs: nextTokenExpEpochMs,
200200
},
201201
},
202202
};
@@ -215,8 +215,8 @@ export async function consume(arg) {
215215
sessionData: {
216216
token1Hash: arg.sessionData.token1Hash,
217217
token2Hash: null,
218-
sessionExp: arg.sessionData.sessionExp,
219-
tokenExp: arg.sessionData.tokenExp,
218+
sessionExpEpochMs: arg.sessionData.sessionExpEpochMs,
219+
tokenExpEpochMs: arg.sessionData.tokenExpEpochMs,
220220
},
221221
},
222222
};

0 commit comments

Comments
 (0)