@stonyx/oauth 0.1.1-alpha.30 → 0.1.1-alpha.32
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/ticket-store.d.ts +16 -0
- package/dist/ticket-store.js +23 -4
- package/package.json +1 -1
- package/src/ticket-store.ts +24 -4
package/dist/ticket-store.d.ts
CHANGED
|
@@ -75,8 +75,24 @@ export interface RedeemedTicket {
|
|
|
75
75
|
* The extraction is tracked at `abofs/stonyx-oauth#58`.
|
|
76
76
|
*/
|
|
77
77
|
export default class TicketStore {
|
|
78
|
+
/**
|
|
79
|
+
* Live tickets, keyed by the **SHA-256 of the ticket**, never by the ticket.
|
|
80
|
+
*
|
|
81
|
+
* Same discipline as `OAuth.pendingStates`, which holds `bindingHash` and
|
|
82
|
+
* never the binding value: a ticket is a client-presented secret looked up
|
|
83
|
+
* server-side, so a heap dump, a debug serialisation or an accidental log of
|
|
84
|
+
* this map should yield a useless digest rather than a live redeemable
|
|
85
|
+
* credential. The two secret stores in this module now agree.
|
|
86
|
+
*
|
|
87
|
+
* No constant-time comparison is needed and none is used: lookup is a hash
|
|
88
|
+
* probe on a 256-bit high-entropy key, not a secret-dependent byte
|
|
89
|
+
* comparison, so there is no early-exit timing signal to exploit. That is
|
|
90
|
+
* the same reason `redeem` can stay an ordinary `Map.get`.
|
|
91
|
+
*/
|
|
78
92
|
tickets: Map<string, TicketRecord>;
|
|
79
93
|
ttl: number;
|
|
94
|
+
/** SHA-256 of a ticket, hex — the only form this store keeps on the heap. */
|
|
95
|
+
static hash(ticket: string): string;
|
|
80
96
|
/**
|
|
81
97
|
* Mints a ticket for a freshly created session.
|
|
82
98
|
*
|
package/dist/ticket-store.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
/**
|
|
3
3
|
* Lifetime of an exchange ticket.
|
|
4
4
|
*
|
|
@@ -67,8 +67,26 @@ export const TICKET_BYTES = 32;
|
|
|
67
67
|
* The extraction is tracked at `abofs/stonyx-oauth#58`.
|
|
68
68
|
*/
|
|
69
69
|
export default class TicketStore {
|
|
70
|
+
/**
|
|
71
|
+
* Live tickets, keyed by the **SHA-256 of the ticket**, never by the ticket.
|
|
72
|
+
*
|
|
73
|
+
* Same discipline as `OAuth.pendingStates`, which holds `bindingHash` and
|
|
74
|
+
* never the binding value: a ticket is a client-presented secret looked up
|
|
75
|
+
* server-side, so a heap dump, a debug serialisation or an accidental log of
|
|
76
|
+
* this map should yield a useless digest rather than a live redeemable
|
|
77
|
+
* credential. The two secret stores in this module now agree.
|
|
78
|
+
*
|
|
79
|
+
* No constant-time comparison is needed and none is used: lookup is a hash
|
|
80
|
+
* probe on a 256-bit high-entropy key, not a secret-dependent byte
|
|
81
|
+
* comparison, so there is no early-exit timing signal to exploit. That is
|
|
82
|
+
* the same reason `redeem` can stay an ordinary `Map.get`.
|
|
83
|
+
*/
|
|
70
84
|
tickets = new Map();
|
|
71
85
|
ttl = TICKET_TTL_MS;
|
|
86
|
+
/** SHA-256 of a ticket, hex — the only form this store keeps on the heap. */
|
|
87
|
+
static hash(ticket) {
|
|
88
|
+
return createHash('sha256').update(ticket).digest('hex');
|
|
89
|
+
}
|
|
72
90
|
/**
|
|
73
91
|
* Mints a ticket for a freshly created session.
|
|
74
92
|
*
|
|
@@ -77,7 +95,7 @@ export default class TicketStore {
|
|
|
77
95
|
*/
|
|
78
96
|
issue(sessionId, expiresAt) {
|
|
79
97
|
const ticket = randomBytes(TICKET_BYTES).toString('base64url');
|
|
80
|
-
this.tickets.set(ticket, { sessionId, expiresAt, createdAt: Date.now() });
|
|
98
|
+
this.tickets.set(TicketStore.hash(ticket), { sessionId, expiresAt, createdAt: Date.now() });
|
|
81
99
|
return ticket;
|
|
82
100
|
}
|
|
83
101
|
/**
|
|
@@ -95,10 +113,11 @@ export default class TicketStore {
|
|
|
95
113
|
* holder of a ticket they did not mint has no business having.
|
|
96
114
|
*/
|
|
97
115
|
redeem(ticket) {
|
|
98
|
-
const
|
|
116
|
+
const key = ticket ? TicketStore.hash(ticket) : null;
|
|
117
|
+
const record = key ? this.tickets.get(key) : undefined;
|
|
99
118
|
if (!record)
|
|
100
119
|
return null;
|
|
101
|
-
this.tickets.delete(
|
|
120
|
+
this.tickets.delete(key);
|
|
102
121
|
if (Date.now() - record.createdAt > this.ttl)
|
|
103
122
|
return null;
|
|
104
123
|
return { sessionId: record.sessionId, expiresAt: record.expiresAt };
|
package/package.json
CHANGED
package/src/ticket-store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Lifetime of an exchange ticket.
|
|
@@ -81,9 +81,28 @@ export interface RedeemedTicket {
|
|
|
81
81
|
* The extraction is tracked at `abofs/stonyx-oauth#58`.
|
|
82
82
|
*/
|
|
83
83
|
export default class TicketStore {
|
|
84
|
+
/**
|
|
85
|
+
* Live tickets, keyed by the **SHA-256 of the ticket**, never by the ticket.
|
|
86
|
+
*
|
|
87
|
+
* Same discipline as `OAuth.pendingStates`, which holds `bindingHash` and
|
|
88
|
+
* never the binding value: a ticket is a client-presented secret looked up
|
|
89
|
+
* server-side, so a heap dump, a debug serialisation or an accidental log of
|
|
90
|
+
* this map should yield a useless digest rather than a live redeemable
|
|
91
|
+
* credential. The two secret stores in this module now agree.
|
|
92
|
+
*
|
|
93
|
+
* No constant-time comparison is needed and none is used: lookup is a hash
|
|
94
|
+
* probe on a 256-bit high-entropy key, not a secret-dependent byte
|
|
95
|
+
* comparison, so there is no early-exit timing signal to exploit. That is
|
|
96
|
+
* the same reason `redeem` can stay an ordinary `Map.get`.
|
|
97
|
+
*/
|
|
84
98
|
tickets = new Map<string, TicketRecord>();
|
|
85
99
|
ttl = TICKET_TTL_MS;
|
|
86
100
|
|
|
101
|
+
/** SHA-256 of a ticket, hex — the only form this store keeps on the heap. */
|
|
102
|
+
static hash(ticket: string): string {
|
|
103
|
+
return createHash('sha256').update(ticket).digest('hex');
|
|
104
|
+
}
|
|
105
|
+
|
|
87
106
|
/**
|
|
88
107
|
* Mints a ticket for a freshly created session.
|
|
89
108
|
*
|
|
@@ -92,7 +111,7 @@ export default class TicketStore {
|
|
|
92
111
|
*/
|
|
93
112
|
issue(sessionId: string, expiresAt: number): string {
|
|
94
113
|
const ticket = randomBytes(TICKET_BYTES).toString('base64url');
|
|
95
|
-
this.tickets.set(ticket, { sessionId, expiresAt, createdAt: Date.now() });
|
|
114
|
+
this.tickets.set(TicketStore.hash(ticket), { sessionId, expiresAt, createdAt: Date.now() });
|
|
96
115
|
return ticket;
|
|
97
116
|
}
|
|
98
117
|
|
|
@@ -111,10 +130,11 @@ export default class TicketStore {
|
|
|
111
130
|
* holder of a ticket they did not mint has no business having.
|
|
112
131
|
*/
|
|
113
132
|
redeem(ticket: string): RedeemedTicket | null {
|
|
114
|
-
const
|
|
133
|
+
const key = ticket ? TicketStore.hash(ticket) : null;
|
|
134
|
+
const record = key ? this.tickets.get(key) : undefined;
|
|
115
135
|
if (!record) return null;
|
|
116
136
|
|
|
117
|
-
this.tickets.delete(
|
|
137
|
+
this.tickets.delete(key!);
|
|
118
138
|
|
|
119
139
|
if (Date.now() - record.createdAt > this.ttl) return null;
|
|
120
140
|
|