- sessionKey.ts: one 32-byte base64url key per form mount, sent only in headers - api/files.ts: FileRoutes over the record and child file routes, XHR upload with progress, 401 refresh and retry - FileuploadField and FileCaptionModal per UI-SPEC section 3: dropzone, image grid, rows, per-item states, client pre-checks, reorder, protected previews - FormView provides FORM_SESSION, counts pending changes as dirty and sends X-Session-Key on create and update - fileupload lang keys in en and pl, admin-spa docs note, deferred smoke test, rebuilt dist
29 lines
1.1 KiB
TypeScript
29 lines
1.1 KiB
TypeScript
// Form session keys (D-02). A record form makes one key when it mounts and a
|
|
// relation child modal makes its own when it opens. Uploads, file removals,
|
|
// deferred relation calls and the final save carry the key in a header, so
|
|
// the server can hold the work against it and commit it with the save. A key
|
|
// never appears in a URL: it would end up in logs and the browser history.
|
|
|
|
/** Header of the record form's key. */
|
|
export const SESSION_HEADER = 'X-Session-Key'
|
|
|
|
/** Header of a relation child modal's own key. */
|
|
export const CHILD_SESSION_HEADER = 'X-Child-Session-Key'
|
|
|
|
/** Random bytes per key: 256 bits, above D-02's 128-bit floor. */
|
|
const KEY_BYTES = 32
|
|
|
|
/**
|
|
* A new key: 32 bytes from crypto.getRandomValues encoded as unpadded
|
|
* base64url, 43 characters of A-Z a-z 0-9 _ - (the server's key pattern).
|
|
*/
|
|
export function newSessionKey(): string {
|
|
const bytes = new Uint8Array(KEY_BYTES)
|
|
globalThis.crypto.getRandomValues(bytes)
|
|
let binary = ''
|
|
for (const byte of bytes) {
|
|
binary += String.fromCharCode(byte)
|
|
}
|
|
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
}
|