v0.1 64c03e0

session.server.ts RawHistoryFolder

1import db from '../db/db.server';
2import { sessions, users } from '../db/schema';
3import { eq } from 'drizzle-orm';
4import type { Session, User } from '../types/chat';
5
6// Helper: Compute SHA-256 hash of a message and encode it as a hex string.
7export async function sha256(message: string): Promise<string> {
8  const encoder = new TextEncoder();
9  const data = encoder.encode(message);
10  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
11  const hashArray = Array.from(new Uint8Array(hashBuffer));
12  const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
13  return hashHex;
14}
15
16// Helper: Custom Base32 encoder using the standard alphabet.
17// We use this instead of @oslojs/encoding given Cloudflare Pages compatibility.
18const base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
19function base32Encode(data: Uint8Array): string {
20  let bits = 0;
21  let value = 0;
22  let output = "";
23  for (let i = 0; i < data.length; i++) {
24    value = (value << 8) | data[i];
25    bits += 8;
26    while (bits >= 5) {
27      const index = (value >>> (bits - 5)) & 0x1f;
28      output += base32Alphabet[index];
29      bits -= 5;
30    }
31  }
32  if (bits > 0) {
33    const index = (value << (5 - bits)) & 0x1f;
34    output += base32Alphabet[index];
35  }
36  return output;
37}
38
39// Generate a secure session token by generating 20 random bytes and Base32 encoding them.
40export function generateSessionToken(): string {
41  const randomBytesArray = new Uint8Array(20);
42  crypto.getRandomValues(randomBytesArray);
43  const token = base32Encode(randomBytesArray);
44  console.debug("Generated session token:", token);
45  return token;
46}
47
48// Create a session record once a valid token is available.
49// The session ID is the SHA-256 hash of the token.
50export async function createSession(token: string, userId: string): Promise<Session> {
51  console.debug("Creating session for user:", userId);
52  const sessionId = await sha256(token);
53  const createdAt = Date.now();
54  const expiresAt = createdAt + 24 * 60 * 60 * 1000; // 24 hours expiry
55  await db.insert(sessions).values({
56    id: sessionId,
57    userId,
58    createdAt,
59    expiresAt,
60  });
61  console.debug("Session created with id:", sessionId);
62  return { id: sessionId, userId, createdAt, expiresAt };
63}
64
65/**
66 * Type distributed through our API.
67 * Returns an object with both session and user if valid, or nulls if not.
68 */
69export type SessionValidationResult =
70  | { session: Session; user: User }
71  | { session: null; user: null };
72
73// Validate a session token by converting it to its SHA‑256 hash, checking expiration, and fetching the user.
74export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
75  console.debug("Validating session token:", token);
76  const sessionId = await sha256(token);
77  const session = await db.query.sessions.findFirst({
78    where: eq(sessions.id, sessionId)
79  });
80  if (!session) {
81    console.debug("Session not found for id:", sessionId);
82    return { session: null, user: null };
83  }
84  const now = Date.now();
85  if (session.expiresAt < now) {
86    console.debug("Session expired for id:", sessionId);
87    return { session: null, user: null };
88  }
89  const user = await db.query.users.findFirst({
90    where: eq(users.id, session.userId)
91  });
92  if (!user) {
93    console.debug("User not found for session id:", sessionId);
94    return { session: null, user: null };
95  }
96  console.debug("Session validated for id:", sessionId);
97  return { session, user };
98}
99
100// Invalidate a specific session by deleting it from the database.
101export async function invalidateSession(sessionId: string): Promise<void> {
102  console.debug("Invalidating session with id:", sessionId);
103  await db.delete(sessions).where(eq(sessions.id, sessionId));
104  console.debug("Session invalidated:", sessionId);
105}
106
107// Invalidate all sessions for a given user.
108export async function invalidateAllSessions(userId: string): Promise<void> {
109  console.debug("Invalidating all sessions for user:", userId);
110  await db.delete(sessions).where(eq(sessions.userId, userId));
111  console.debug("All sessions invalidated for user:", userId);
112} 

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 9

↑↓ moveenter openesc close