| ... | @@ -2,7 +2,6 @@ | ... | @@ -2,7 +2,6 @@ |
| 2 | | 2 | |
| 3 | const std = @import("std"); | 3 | const std = @import("std"); |
| 4 | const string = []const u8; | 4 | const string = []const u8; |
| 5 | const http = @import("apple_pie"); | | |
| 6 | const files = @import("self/files"); | 5 | const files = @import("self/files"); |
| 7 | const pek = @import("pek"); | 6 | const pek = @import("pek"); |
| 8 | const zfetch = @import("zfetch"); | 7 | const zfetch = @import("zfetch"); |
| ... | @@ -214,35 +213,28 @@ pub fn clientByProviderId(clients: []const Client, name: string) ?Client { | ... | @@ -214,35 +213,28 @@ pub fn clientByProviderId(clients: []const Client, name: string) ?Client { |
| 214 | return null; | 213 | return null; |
| 215 | } | 214 | } |
| 216 | | 215 | |
| 217 | pub const IsLoggedInFn = fn (http.Request) anyerror!bool; | 216 | pub const IsLoggedInFn = fn (*std.http.Server.Response) anyerror!bool; |
| 218 | | 217 | |
| 219 | pub fn Handlers(comptime T: type) type { | 218 | pub fn Handlers(comptime T: type) type { |
| 220 | comptime std.debug.assert(@hasDecl(T, "Ctx")); | | |
| 221 | comptime std.debug.assert(@hasDecl(T, "isLoggedIn")); | 219 | comptime std.debug.assert(@hasDecl(T, "isLoggedIn")); |
| 222 | comptime std.debug.assert(@hasDecl(T, "doneUrl")); | 220 | comptime std.debug.assert(@hasDecl(T, "doneUrl")); |
| 223 | comptime std.debug.assert(@hasDecl(T, "saveInfo")); | 221 | comptime std.debug.assert(@hasDecl(T, "saveInfo")); |
| | 222 | comptime std.debug.assert(@hasDecl(T, "callbackPath")); |
| 224 | | 223 | |
| 225 | return struct { | 224 | return struct { |
| 226 | const Self = @This(); | 225 | const Self = @This(); |
| 227 | pub var clients: []Client = &.{}; | 226 | pub var clients: []Client = &.{}; |
| 228 | pub var callbackPath: string = ""; | | |
| 229 | | | |
| 230 | pub fn login(_: T.Ctx, response: *http.Response, request: http.Request, captures: ?*const anyopaque) !void { | | |
| 231 | std.debug.assert(captures == null); | | |
| 232 | | | |
| 233 | const alloc = request.arena; | | |
| 234 | const query = try request.context.uri.queryParameters(alloc); | | |
| 235 | | 227 | |
| | 228 | pub fn login(response: *std.http.Server.Response, body_writer: anytype, alloc: std.mem.Allocator, query: UrlValues) !void { |
| 236 | if (query.get("with")) |with| { | 229 | if (query.get("with")) |with| { |
| 237 | const client = clientByProviderId(Self.clients, with) orelse return try fail(response, "Client with that ID not found!\n", .{}); | 230 | const client = clientByProviderId(Self.clients, with) orelse return try fail(response, body_writer, "Client with that ID not found!\n", .{}); |
| 238 | return try loginOne(response, request, T, client, Self.callbackPath); | 231 | return try loginOne(response, alloc, T, client, T.callbackPath); |
| 239 | } | 232 | } |
| 240 | | | |
| 241 | if (Self.clients.len == 1) { | 233 | if (Self.clients.len == 1) { |
| 242 | return try loginOne(response, request, T, clients[0], Self.callbackPath); | 234 | return try loginOne(response, alloc, T, clients[0], T.callbackPath); |
| 243 | } | 235 | } |
| 244 | | 236 | |
| 245 | try response.headers.put("Content-Type", "text/html"); | 237 | try response.headers.append("Content-Type", "text/html"); |
| 246 | const page = files.@"/selector.pek"; | 238 | const page = files.@"/selector.pek"; |
| 247 | const tmpl = comptime pek.parse(page); | 239 | const tmpl = comptime pek.parse(page); |
| 248 | try pek.compile(Base, alloc, response.writer(), tmpl, .{ | 240 | try pek.compile(Base, alloc, response.writer(), tmpl, .{ |
| ... | @@ -250,22 +242,17 @@ pub fn Handlers(comptime T: type) type { | ... | @@ -250,22 +242,17 @@ pub fn Handlers(comptime T: type) type { |
| 250 | }); | 242 | }); |
| 251 | } | 243 | } |
| 252 | | 244 | |
| 253 | pub fn callback(_: T.Ctx, response: *http.Response, request: http.Request, captures: ?*const anyopaque) !void { | 245 | pub fn callback(response: *std.http.Server.Response, body_writer: anytype, alloc: std.mem.Allocator, query: UrlValues) !void { |
| 254 | std.debug.assert(captures == null); | 246 | const state = query.get("state") orelse return try fail(response, body_writer, "", .{}); |
| 255 | | 247 | const client = clientByProviderId(Self.clients, state) orelse return try fail(response, body_writer, "error: No handler found for provider: {s}\n", .{state}); |
| 256 | const alloc = request.arena; | 248 | const code = query.get("code") orelse return try fail(response, body_writer, "", .{}); |
| 257 | const query = try request.context.uri.queryParameters(alloc); | | |
| 258 | | | |
| 259 | const state = query.get("state") orelse return try fail(response, "", .{}); | | |
| 260 | const client = clientByProviderId(Self.clients, state) orelse return try fail(response, "error: No handler found for provider: {s}\n", .{state}); | | |
| 261 | const code = query.get("code") orelse return try fail(response, "", .{}); | | |
| 262 | | 249 | |
| 263 | var params = UrlValues.init(alloc); | 250 | var params = UrlValues.init(alloc); |
| 264 | try params.add("client_id", client.id); | 251 | try params.add("client_id", client.id); |
| 265 | try params.add("client_secret", client.secret); | 252 | try params.add("client_secret", client.secret); |
| 266 | try params.add("grant_type", "authorization_code"); | 253 | try params.add("grant_type", "authorization_code"); |
| 267 | try params.add("code", code); | 254 | try params.add("code", code); |
| 268 | try params.add("redirect_uri", try redirectUri(alloc, request, callbackPath)); | 255 | try params.add("redirect_uri", try redirectUri(response, alloc, T.callbackPath)); |
| 269 | try params.add("state", "none"); | 256 | try params.add("state", "none"); |
| 270 | | 257 | |
| 271 | const req = try zfetch.Request.init(alloc, client.provider.token_url, null); | 258 | const req = try zfetch.Request.init(alloc, client.provider.token_url, null); |
| ... | @@ -298,49 +285,49 @@ pub fn Handlers(comptime T: type) type { | ... | @@ -298,49 +285,49 @@ pub fn Handlers(comptime T: type) type { |
| 298 | const name = val2.root.object.get(client.provider.name_prop).?.string; | 285 | const name = val2.root.object.get(client.provider.name_prop).?.string; |
| 299 | try T.saveInfo(response, alloc, client.provider, id, name, val.root, val2.root); | 286 | try T.saveInfo(response, alloc, client.provider, id, name, val.root, val2.root); |
| 300 | | 287 | |
| 301 | try response.headers.put("Location", T.doneUrl); | 288 | try response.headers.append("Location", T.doneUrl); |
| 302 | try response.writeHeader(.found); | 289 | response.status = .found; |
| 303 | } | 290 | } |
| 304 | }; | 291 | }; |
| 305 | } | 292 | } |
| 306 | | 293 | |
| 307 | fn loginOne(response: *http.Response, request: http.Request, comptime T: type, client: Client, callbackPath: string) !void { | 294 | fn loginOne(response: *std.http.Server.Response, alloc: std.mem.Allocator, comptime T: type, client: Client, callbackPath: string) !void { |
| 308 | if (try T.isLoggedIn(request)) { | 295 | if (try T.isLoggedIn(response, alloc)) { |
| 309 | try response.headers.put("Location", T.doneUrl); | 296 | try response.headers.append("Location", T.doneUrl); |
| 310 | } else { | 297 | } else { |
| 311 | const alloc = request.arena; | | |
| 312 | const idp = client.provider; | 298 | const idp = client.provider; |
| 313 | var params = UrlValues.init(alloc); | 299 | var params = UrlValues.init(alloc); |
| 314 | try params.add("client_id", client.id); | 300 | try params.add("client_id", client.id); |
| 315 | try params.add("redirect_uri", try redirectUri(alloc, request, callbackPath)); | 301 | try params.add("redirect_uri", try redirectUri(response, alloc, callbackPath)); |
| 316 | try params.add("response_type", "code"); | 302 | try params.add("response_type", "code"); |
| 317 | try params.add("scope", idp.scope); | 303 | try params.add("scope", idp.scope); |
| 318 | try params.add("duration", "temporary"); | 304 | try params.add("duration", "temporary"); |
| 319 | try params.add("state", idp.id); | 305 | try params.add("state", idp.id); |
| 320 | const authurl = try std.mem.join(alloc, "?", &.{ idp.authorize_url, try params.encode() }); | 306 | const authurl = try std.mem.join(alloc, "?", &.{ idp.authorize_url, try params.encode() }); |
| 321 | try response.headers.put("Location", authurl); | 307 | try response.headers.append("Location", authurl); |
| 322 | } | 308 | } |
| 323 | try response.writeHeader(.found); | 309 | response.status = .found; |
| 324 | } | 310 | } |
| 325 | | 311 | |
| 326 | fn fail(response: *http.Response, comptime err: string, args: anytype) !void { | 312 | fn fail(response: *std.http.Server.Response, body_writer: anytype, comptime err: string, args: anytype) !void { |
| 327 | try response.writeHeader(.bad_request); | 313 | response.status = .bad_request; |
| 328 | try response.writer().print(err, args); | 314 | try body_writer.print(err, args); |
| 329 | } | 315 | } |
| 330 | | 316 | |
| 331 | fn redirectUri(alloc: std.mem.Allocator, request: http.Request, callbackPath: string) !string { | 317 | fn redirectUri(response: *std.http.Server.Response, alloc: std.mem.Allocator, callbackPath: string) !string { |
| 332 | const headers = try request.headers(alloc); | 318 | const headers = response.request.headers; |
| 333 | const xproto = headers.get("X-Forwarded-Proto") orelse ""; | 319 | const xproto = headers.getFirstValue("X-Forwarded-Proto") orelse ""; |
| 334 | const maybe_tls = std.mem.eql(u8, xproto, "https"); | 320 | const maybe_tls = std.mem.eql(u8, xproto, "https"); |
| 335 | const proto: string = if (maybe_tls) "https" else "http"; | 321 | const proto: string = if (maybe_tls) "https" else "http"; |
| 336 | return try std.fmt.allocPrint(alloc, "{s}://{s}{s}", .{ proto, request.host().?, callbackPath }); | 322 | const host = response.request.headers.getFirstValue("host").?; |
| | 323 | return try std.fmt.allocPrint(alloc, "{s}://{s}{s}", .{ proto, host, callbackPath }); |
| 337 | } | 324 | } |
| 338 | | 325 | |
| 339 | fn fixId(alloc: std.mem.Allocator, id: json.Value) !string { | 326 | fn fixId(alloc: std.mem.Allocator, id: std.json.Value) !string { |
| 340 | return switch (id) { | 327 | return switch (id) { |
| 341 | .String => |v| return v, | 328 | .string => |v| v, |
| 342 | .Int => |v| return try std.fmt.allocPrint(alloc, "{d}", .{v}), | 329 | .integer => |v| try std.fmt.allocPrint(alloc, "{d}", .{v}), |
| 343 | .Float => |v| return try std.fmt.allocPrint(alloc, "{d}", .{v}), | 330 | .float => |v| try std.fmt.allocPrint(alloc, "{d}", .{v}), |
| 344 | else => unreachable, | 331 | else => unreachable, |
| 345 | }; | 332 | }; |
| 346 | } | 333 | } |