-
-
Notifications
You must be signed in to change notification settings - Fork 79
/
userpass_session_auth.zig
172 lines (149 loc) · 5.8 KB
/
userpass_session_auth.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
const std = @import("std");
const zap = @import("zap");
const Lookup = std.StringHashMap([]const u8);
const auth_lock_pw_table = false;
// see the source for more info
const Authenticator = zap.Auth.UserPassSession(
Lookup,
// we may set this to true if we expect our username -> password map
// to change. in that case the authenticator must lock the table for
// every lookup
auth_lock_pw_table,
);
const loginpath = "/login";
// we bake the login page and its displayed image into the the executable
const loginpage = @embedFile("html/login.html");
const img = @embedFile("./html/Ziggy_the_Ziguana.svg.png");
// global vars yeah!
// in bigger projects, we'd probably make use of zap.Endpoint or
// zap.Middleware and "hide" stuff like authenticators in there
var authenticator: Authenticator = undefined;
// the login page (embedded)
fn on_login(r: zap.Request) void {
r.sendBody(loginpage) catch return;
}
// the "normal page"
fn on_normal_page(r: zap.Request) void {
zap.debug("on_normal_page()\n", .{});
r.sendBody(
\\ <html><body>
\\ <h1>Hello from ZAP!!!</h1>
\\ <p>You are logged in!!!</>
\\ <center><a href="/logout">logout</a></center>
\\ </body></html>
) catch return;
}
// the logged-out page
fn on_logout(r: zap.Request) void {
zap.debug("on_logout()\n", .{});
authenticator.logout(&r);
// note, the link below doesn't matter as the authenticator will send us
// straight to the /login page
r.sendBody(
\\ <html><body>
\\ <p>You are logged out!!!</p>
\\ <br>
\\ <p> <a href="/">Log back in</a></p>
\\ </body></html>
) catch return;
}
fn on_request(r: zap.Request) void {
switch (authenticator.authenticateRequest(&r)) {
.Handled => {
// the authenticator handled the entire request for us.
// that means: it re-directed to the /login page because of a
// missing or invalid session cookie
std.log.info("Auth FAILED -> authenticator handled it", .{});
return;
},
// never returned by this type of authenticator
.AuthFailed => unreachable,
.AuthOK => {
// the authenticator says it is ok to proceed as usual
std.log.info("Auth OK", .{});
// dispatch to target path
if (r.path) |p| {
// used in the login page
// note: our login page is /login
// so, anything that starts with /login will not be touched by
// the authenticator. Hence, we name the img for the /login
// page: /login/Ziggy....png
if (std.mem.startsWith(u8, p, "/login/Ziggy_the_Ziguana.svg.png")) {
r.setContentTypeFromPath() catch unreachable;
r.sendBody(img) catch unreachable;
return;
}
// aha! probably got redirected to /login
if (std.mem.startsWith(u8, p, loginpath)) {
std.log.info(" + for /login --> login page", .{});
return on_login(r);
}
// /logout can be shown since we're authenticated
if (std.mem.startsWith(u8, p, "/logout")) {
std.log.info(" + for /logout --> logout page", .{});
return on_logout(r);
}
// /stop can be executed, as we're authenticated
if (std.mem.startsWith(u8, p, "/stop")) {
std.log.info(" + for /stop --> logout page", .{});
zap.stop();
return on_logout(r);
}
// any other paths will show the normal page
std.log.info(" + --> normal page", .{});
return on_normal_page(r);
}
// if there is no path but we're authenticated, so let's show
// the user something
return on_normal_page(r);
},
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true,
}){};
// we start a block here so the defers will run before we call the gpa
// to detect leaks
{
const allocator = gpa.allocator();
var listener = zap.HttpListener.init(.{
.port = 3000,
.on_request = on_request,
.log = true,
.max_clients = 100000,
});
try listener.listen();
zap.enableDebugLog();
// Usernames -> Passwords for the /login page
// ------------------------------------------
var userpass = Lookup.init(allocator);
defer userpass.deinit();
try userpass.put("zap", "awesome");
// init our authenticator. it will redirect all un-authenticated
// requests to the /login page. on POST of correct username, password
// pair, it will create an ephermal session token and let subsequent
// requests that present the cookie through until, in our case: /logout.
authenticator = try Authenticator.init(
allocator,
&userpass,
.{
.usernameParam = "username", // form param name
.passwordParam = "password", // form param name
.loginPage = loginpath,
.cookieName = "zap-session", // cookie name for session
},
);
defer authenticator.deinit();
std.debug.print("Visit me on http://127.0.0.1:3000\n", .{});
// start worker threads
zap.start(.{
.threads = 2,
.workers = 1,
});
}
// all defers should have run by now
std.debug.print("\n\nSTOPPED!\n\n", .{});
const leaked = gpa.detectLeaks();
std.debug.print("Leaks detected: {}\n", .{leaked});
}