atproto pds in zig pds.zat.dev
pds atproto
24

Configure Feed

Select the types of activity you want to include in your feed.

Use app-compatible email tokens

zzstoatzz (Jun 10, 2026, 2:10 PM -0500) 9582ea17 b089af6e

+32 -27
+2 -13
src/atproto/identity.zig
··· 3 3 const config = @import("../core/config.zig"); 4 4 const mail = @import("../core/mail.zig"); 5 5 const http_api = @import("../http/api.zig"); 6 + const email_tokens = @import("../internal/email_tokens.zig"); 6 7 const scopes = @import("../internal/scopes.zig"); 7 8 const plc = @import("plc.zig"); 8 9 const store = @import("../storage/store.zig"); ··· 53 54 return http_api.xrpcError(request, .not_found, "AccountNotFound", "Account not found"); 54 55 }; 55 56 var code_buf: [11]u8 = undefined; 56 - const code = makeCode(&code_buf); 57 + const code = email_tokens.makeCode(&code_buf); 57 58 try store.setAuthCode(account.did, code, store.nowMs() + (10 * 60 * 1000)); 58 59 try mail.sendCode(store.currentIo(), allocator, info.email, account.handle, "PLC operation", "PLC operation", code); 59 60 return http_api.json(request, .ok, "{}"); ··· 421 422 else => false, 422 423 }; 423 424 } 424 - 425 - fn makeCode(out: *[11]u8) []const u8 { 426 - const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; 427 - var random: [10]u8 = undefined; 428 - store.randomBytes(&random); 429 - for (random, 0..) |byte, idx| { 430 - const write_idx = if (idx < 5) idx else idx + 1; 431 - out[write_idx] = alphabet[byte % alphabet.len]; 432 - } 433 - out[5] = '-'; 434 - return out[0..]; 435 - }
+3 -14
src/atproto/server.zig
··· 6 6 const plc = @import("plc.zig"); 7 7 const sync = @import("sync.zig"); 8 8 const http_api = @import("../http/api.zig"); 9 + const email_tokens = @import("../internal/email_tokens.zig"); 9 10 const scopes = @import("../internal/scopes.zig"); 10 11 const store = @import("../storage/store.zig"); 11 12 const zat = @import("zat"); ··· 1064 1065 return http_api.xrpcError(request, .bad_request, "InvalidRequest", "email already confirmed"); 1065 1066 } 1066 1067 var code_buf: [11]u8 = undefined; 1067 - const code = makeCode(&code_buf); 1068 + const code = email_tokens.makeCode(&code_buf); 1068 1069 try store.setAuthCode(account.did, code, expiresInTenMinutes()); 1069 1070 try sendCode(allocator, info.email, account.handle, "Confirm email", "email confirmation", code); 1070 1071 return http_api.json(request, .ok, "{}"); ··· 1114 1115 return http_api.json(request, .ok, "{\"tokenRequired\":false}"); 1115 1116 } 1116 1117 var code_buf: [11]u8 = undefined; 1117 - const code = makeCode(&code_buf); 1118 + const code = email_tokens.makeCode(&code_buf); 1118 1119 try store.setAuthCode(account.did, code, expiresInTenMinutes()); 1119 1120 try sendCode(allocator, info.email, account.handle, "Update email", "email update", code); 1120 1121 return http_api.json(request, .ok, "{\"tokenRequired\":true}"); ··· 1226 1227 1227 1228 fn expiresInTenMinutes() i64 { 1228 1229 return store.nowMs() + (10 * 60 * 1000); 1229 - } 1230 - 1231 - fn makeCode(out: *[11]u8) []const u8 { 1232 - const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; 1233 - var random: [10]u8 = undefined; 1234 - store.randomBytes(&random); 1235 - for (random, 0..) |byte, idx| { 1236 - const write_idx = if (idx < 5) idx else idx + 1; 1237 - out[write_idx] = alphabet[byte % alphabet.len]; 1238 - } 1239 - out[5] = '-'; 1240 - return out[0..]; 1241 1230 } 1242 1231 1243 1232 fn generateAppPassword(allocator: std.mem.Allocator) ![]const u8 {
+26
src/internal/email_tokens.zig
··· 1 + const std = @import("std"); 2 + const store = @import("../storage/store.zig"); 3 + 4 + pub fn makeCode(out: *[11]u8) []const u8 { 5 + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 6 + var random: [10]u8 = undefined; 7 + store.randomBytes(&random); 8 + for (random, 0..) |byte, idx| { 9 + const write_idx = if (idx < 5) idx else idx + 1; 10 + out[write_idx] = alphabet[byte & 31]; 11 + } 12 + out[5] = '-'; 13 + return out[0..]; 14 + } 15 + 16 + test "email codes use social-app compatible base32 shape" { 17 + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 18 + var out: [11]u8 = undefined; 19 + const code = makeCode(&out); 20 + try std.testing.expectEqual(@as(usize, 11), code.len); 21 + try std.testing.expectEqual(@as(u8, '-'), code[5]); 22 + for (code, 0..) |byte, idx| { 23 + if (idx == 5) continue; 24 + try std.testing.expect(std.mem.indexOfScalar(u8, alphabet, byte) != null); 25 + } 26 + }
+1
src/root.zig
··· 34 34 pub const internal = struct { 35 35 pub const api_reference = @import("internal/api_reference/root.zig"); 36 36 pub const cli = @import("internal/cli.zig"); 37 + pub const email_tokens = @import("internal/email_tokens.zig"); 37 38 pub const passkeys = @import("internal/passkeys.zig"); 38 39 pub const permissioned_data = @import("internal/permissioned_data.zig"); 39 40 pub const scopes = @import("internal/scopes.zig");