decentralized crowd-sourced service status tracker on ATProto

Add callback and logout URL, update layout header

Changed files
+56 -4
views
+52
server.js
··· 81 81 res.json(status); 82 82 }); 83 83 84 + app.get("/oauth/callback", async (req, res) => { 85 + if (!req.query.code || req.query.state !== req.session.oauthState) { 86 + return res.status(400).send("Invalid state or missing code"); 87 + } 88 + 89 + try { 90 + // Exchange code for tokens 91 + const tokenResponse = await fetch("https://bsky.social/oauth/token", { 92 + method: "POST", 93 + headers: { "Content-Type": "application/x-www-form-urlencoded" }, 94 + body: new URLSearchParams({ 95 + grant_type: "authorization_code", 96 + code: req.query.code, 97 + redirect_uri: REDIRECT_URI, 98 + client_id: CLIENT_ID, 99 + }), 100 + }); 101 + 102 + if (!tokenResponse.ok) 103 + throw new Error(`Token error: ${await tokenResponse.text()}`); 104 + 105 + const tokens = await tokenResponse.json(); 106 + console.log("Tokens received:", { 107 + did: tokens.did, 108 + hasAccess: !!tokens.access_token, 109 + }); 110 + 111 + // Log in with the access token 112 + const agent = new BskyAgent({ service: "https://bsky.social" }); 113 + await agent.login({ 114 + identifier: tokens.did, 115 + password: tokens.access_token, 116 + }); 117 + 118 + // Save to session 119 + req.session.did = agent.did; 120 + req.session.handle = agent.session?.data?.handle || tokens.did; 121 + req.session.accessJwt = tokens.access_token; 122 + req.session.refreshJwt = tokens.refresh_token; 123 + 124 + console.log("Logged in as @" + req.session.handle); 125 + res.redirect("/"); 126 + } catch (err) { 127 + console.error("OAuth callback failed:", err); 128 + res.status(500).send("Login failed — check logs"); 129 + } 130 + }); 131 + 132 + app.get("/logout", (req, res) => { 133 + req.session.destroy(() => res.redirect("/")); 134 + }); 135 + 84 136 const PORT = process.env.PORT || 3000; 85 137 app.listen(PORT, () => { 86 138 console.log(`atstatus.net → http://localhost:${PORT}`);
+4 -4
views/layout.ejs
··· 10 10 <header class="header"> 11 11 <div class="container"> 12 12 <a href="/" class="status">@status</a> 13 - <% if (session && session.did) { %> 14 - <span style="opacity:0.8;">@<%= session.handle %></span> 15 - <a href="/logout" class="login-btn">Logout</a> 13 + <% if (session && session.handle) { %> 14 + <span style="opacity:0.8;">@<%= session.handle %></span> 15 + <a href="/logout" class="login-btn">Logout</a> 16 16 <% } else { %> 17 - <a href="/login" class="login-btn">Login</a> 17 + <a href="/login" class="login-btn">Login</a> 18 18 <% } %> 19 19 </div> 20 20 </header>