atproto pds in zig pds.zat.dev
pds atproto
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970const std = @import("std");const auth = @import("../auth/tokens.zig");const clock = @import("../core/clock.zig");const config = @import("../core/config.zig");const http_api = @import("../http/api.zig");const store = @import("../storage/store.zig");
const cookie_name = "__Host-zds-browser";pub const lifetime: i64 = 365 * 24 * 60 * 60;
pub fn token(request: *const http_api.Request) ?[]const u8 { var cookies = std.mem.splitScalar(u8, http_api.headerValue(request, "cookie") orelse return null, ';'); while (cookies.next()) |part| { const cookie = std.mem.trim(u8, part, " \t"); if (std.mem.startsWith(u8, cookie, cookie_name ++ "=")) { const value = cookie[cookie_name.len + 1 ..]; if (value.len != 64) return null; for (value) |c| if (!std.ascii.isHex(c)) return null; return value; } } return null;}
fn digest(value: []const u8) [64]u8 { var hash: [32]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(value, &hash, .{}); return std.fmt.bytesToHex(hash, .lower);}
pub fn account(request: *const http_api.Request, allocator: std.mem.Allocator) !?auth.Account { const value = token(request) orelse return null; return store.getBrowserSession(allocator, &digest(value), clock.nowSeconds());}
pub fn sameOrigin(request: *const http_api.Request) bool { const origin = http_api.headerValue(request, "origin") orelse return false; return std.mem.eql(u8, origin, config.publicUrl());}
pub fn csrf(value: []const u8, request_id: []const u8) [64]u8 { var mac: [32]u8 = undefined; std.crypto.auth.hmac.sha2.HmacSha256.create(&mac, request_id, value); return std.fmt.bytesToHex(mac, .lower);}
pub fn validCsrf(request: *const http_api.Request, request_id: []const u8, submitted: []const u8) bool { if (!sameOrigin(request) or submitted.len != 64) return false; const value = token(request) orelse return false; return std.crypto.timing_safe.eql([64]u8, csrf(value, request_id), submitted[0..64].*);}
pub fn remember(request: *const http_api.Request, allocator: std.mem.Allocator, user: auth.Account) !void { const value = try store.randomToken(allocator, "", 32); try store.putBrowserSession(&digest(value), user, clock.nowSeconds() + lifetime); if (token(request)) |old| try store.deleteBrowserSession(&digest(old)); const cookie = try std.fmt.allocPrint(allocator, cookie_name ++ "={s}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age={d}", .{ value, lifetime }); try http_api.addResponseHeader("set-cookie", cookie);}
pub fn forget(request: *const http_api.Request) !void { if (token(request)) |value| try store.deleteBrowserSession(&digest(value)); try http_api.addResponseHeader("set-cookie", cookie_name ++ "=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0");}
test "browser consent is bound to both browser and authorization request" { try std.testing.expect(!std.mem.eql(u8, &csrf("browser-a", "request-a"), &csrf("browser-b", "request-a"))); try std.testing.expect(!std.mem.eql(u8, &csrf("browser-a", "request-a"), &csrf("browser-a", "request-b")));}