Skip to content

Commit 2c52792

Browse files
authored
Merge pull request #174 from sc68cal/login_example
Add an example for using auth for logging in
2 parents a46e944 + 327b039 commit 2c52792

File tree

12 files changed

+173
-51
lines changed

12 files changed

+173
-51
lines changed

Diff for: .github/workflows/CI.yml

+14
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ jobs:
1818
os: [ubuntu-latest, macos-latest, windows-latest]
1919
runs-on: ${{ matrix.os }}
2020
steps:
21+
# Create postgres server
22+
# https://github.com/marketplace/actions/setup-postgresql-for-linux-macos-windows
23+
- uses: ikalnytskyi/action-setup-postgres@v7
24+
2125
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
2226
- uses: actions/checkout@v3
2327
with:
@@ -40,7 +44,17 @@ jobs:
4044
- name: Run App Tests
4145
run: |
4246
cd demo
47+
zig build -Denvironment=testing jetzig:database:create
48+
zig build -Denvironment=testing jetzig:database:migrate
4349
zig build -Denvironment=testing jetzig:test
50+
env:
51+
JETQUERY_HOSTNAME: 'localhost'
52+
JETQUERY_USERNAME: 'postgres'
53+
JETQUERY_PASSWORD: 'postgres'
54+
JETQUERY_DATABASE: 'test'
55+
# Assume a small amount of connections are allowed
56+
# into postgres
57+
JETQUERY_POOL_SIZE: 2
4458

4559
- name: Build artifacts
4660
if: ${{ matrix.os == 'ubuntu-latest' }}

Diff for: cli/commands/init.zig

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ pub fn run(
106106
try copySourceFile(
107107
allocator,
108108
install_dir,
109-
"demo/config/database.zig",
109+
"demo/config/database_template.zig",
110110
"config/database.zig",
111111
null,
112112
);

Diff for: cli/compile.zig

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn initDataModule(build: *std.Build) !*std.Build.Module {
3030
"demo/public/zmpl.png",
3131
"demo/public/favicon.ico",
3232
"demo/public/styles.css",
33-
"demo/config/database.zig",
33+
"demo/config/database_template.zig",
3434
".gitignore",
3535
};
3636

Diff for: demo/config/database.zig

+3-44
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,7 @@
11
pub const database = .{
2-
// Null adapter fails when a database call is invoked.
3-
.development = .{
4-
.adapter = .null,
5-
},
2+
// This configuration is used for CI
3+
// in GitHub
64
.testing = .{
7-
.adapter = .null,
8-
},
9-
.production = .{
10-
.adapter = .null,
5+
.adapter = .postgresql,
116
},
12-
// PostgreSQL adapter configuration.
13-
//
14-
// All options except `adapter` can be configured using environment variables:
15-
//
16-
// * JETQUERY_HOSTNAME
17-
// * JETQUERY_PORT
18-
// * JETQUERY_USERNAME
19-
// * JETQUERY_PASSWORD
20-
// * JETQUERY_DATABASE
21-
//
22-
// .testing = .{
23-
// .adapter = .postgresql,
24-
// .hostname = "localhost",
25-
// .port = 5432,
26-
// .username = "postgres",
27-
// .password = "password",
28-
// .database = "myapp_testing",
29-
// },
30-
//
31-
// .development = .{
32-
// .adapter = .postgresql,
33-
// .hostname = "localhost",
34-
// .port = 5432,
35-
// .username = "postgres",
36-
// .password = "password",
37-
// .database = "myapp_development",
38-
// },
39-
//
40-
// .production = .{
41-
// .adapter = .postgresql,
42-
// .hostname = "localhost",
43-
// .port = 5432,
44-
// .username = "postgres",
45-
// .password = "password",
46-
// .database = "myapp_production",
47-
// },
487
};

Diff for: demo/config/database_template.zig

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
pub const database = .{
2+
// Null adapter fails when a database call is invoked.
3+
.development = .{
4+
.adapter = .null,
5+
},
6+
.testing = .{
7+
.adapter = .null,
8+
},
9+
.production = .{
10+
.adapter = .null,
11+
},
12+
// PostgreSQL adapter configuration.
13+
//
14+
// All options except `adapter` can be configured using environment variables:
15+
//
16+
// * JETQUERY_HOSTNAME
17+
// * JETQUERY_PORT
18+
// * JETQUERY_USERNAME
19+
// * JETQUERY_PASSWORD
20+
// * JETQUERY_DATABASE
21+
//
22+
// .testing = .{
23+
// .adapter = .postgresql,
24+
// .hostname = "localhost",
25+
// .port = 5432,
26+
// .username = "postgres",
27+
// .password = "password",
28+
// .database = "myapp_testing",
29+
// },
30+
//
31+
// .development = .{
32+
// .adapter = .postgresql,
33+
// .hostname = "localhost",
34+
// .port = 5432,
35+
// .username = "postgres",
36+
// .password = "password",
37+
// .database = "myapp_development",
38+
// },
39+
//
40+
// .production = .{
41+
// .adapter = .postgresql,
42+
// .hostname = "localhost",
43+
// .port = 5432,
44+
// .username = "postgres",
45+
// .password = "password",
46+
// .database = "myapp_production",
47+
// },
48+
};

Diff for: demo/src/app/database/Schema.zig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const jetquery = @import("jetzig").jetquery;
2+
3+
pub const User = jetquery.Model(@This(), "users", struct {
4+
id: i32,
5+
email: []const u8,
6+
password_hash: []const u8,
7+
created_at: jetquery.DateTime,
8+
updated_at: jetquery.DateTime,
9+
}, .{});
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
const jetquery = @import("jetquery");
22

3-
pub fn up(repo: *jetquery.Repo) !void {
3+
pub fn up(repo: anytype) !void {
44
_ = repo;
55
}
66

7-
pub fn down(repo: *jetquery.Repo) !void {
7+
pub fn down(repo: anytype) !void {
88
_ = repo;
99
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const std = @import("std");
2+
const jetquery = @import("jetquery");
3+
const t = jetquery.schema.table;
4+
5+
pub fn up(repo: anytype) !void {
6+
try repo.createTable(
7+
"users",
8+
&.{
9+
t.primaryKey("id", .{}),
10+
t.column("email", .string, .{ .unique = true, .index = true }),
11+
t.column("password_hash", .string, .{}),
12+
t.timestamps(.{}),
13+
},
14+
.{},
15+
);
16+
}
17+
18+
pub fn down(repo: anytype) !void {
19+
try repo.dropTable("users", .{});
20+
}

Diff for: demo/src/app/views/login.zig

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const std = @import("std");
2+
const jetzig = @import("jetzig");
3+
const auth = @import("jetzig").auth;
4+
5+
pub fn index(request: *jetzig.Request) !jetzig.View {
6+
return request.render(.ok);
7+
}
8+
9+
pub fn post(request: *jetzig.Request) !jetzig.View {
10+
const Login = struct {
11+
email: []const u8,
12+
password: []const u8,
13+
};
14+
15+
const params = try request.expectParams(Login) orelse {
16+
return request.fail(.forbidden);
17+
};
18+
19+
// Lookup the user by email
20+
const query = jetzig.database.Query(.User).findBy(
21+
.{ .email = params.email },
22+
);
23+
24+
const user = try request.repo.execute(query) orelse {
25+
return request.fail(.forbidden);
26+
};
27+
28+
// Check that the password matches
29+
if (try auth.verifyPassword(
30+
request.allocator,
31+
user.password_hash,
32+
params.password,
33+
)) {
34+
try auth.signIn(request, user.id);
35+
return request.redirect("/", .found);
36+
}
37+
return request.fail(.forbidden);
38+
}
39+
40+
test "post" {
41+
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
42+
defer app.deinit();
43+
44+
const hashed_pass = try auth.hashPassword(std.testing.allocator, "test");
45+
defer std.testing.allocator.free(hashed_pass);
46+
47+
try jetzig.database.Query(.User).deleteAll().execute(app.repo);
48+
try app.repo.insert(.User, .{
49+
.id = 1,
50+
.email = "[email protected]",
51+
.password_hash = hashed_pass,
52+
});
53+
54+
const response = try app.request(.POST, "/login", .{
55+
.json = .{
56+
.email = "[email protected]",
57+
.password = "test",
58+
},
59+
});
60+
try response.expectStatus(.found);
61+
}

Diff for: demo/src/app/views/login/index.zmpl

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<form method="post" id="login">
2+
<input type="email" name="email" placeholder="[email protected]">
3+
<label for="email">Email address</label>
4+
<input type="password" name="password" placeholder="Password">
5+
<label for="password">Password</label>
6+
<button type="submit" form="login">Sign in</button>
7+
</form>

Diff for: src/jetzig/auth.zig

+6-3
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@ pub fn verifyPassword(
4242
}
4343

4444
pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]const u8 {
45-
const buf = try allocator.alloc(u8, 128);
46-
return try std.crypto.pwhash.argon2.strHash(
45+
var buf: [128]u8 = undefined;
46+
const hash = try std.crypto.pwhash.argon2.strHash(
4747
password,
4848
.{
4949
.allocator = allocator,
5050
.params = .{ .t = 3, .m = 32, .p = 4 },
5151
},
52-
buf,
52+
&buf,
5353
);
54+
const result = try allocator.alloc(u8, hash.len);
55+
@memcpy(result, hash);
56+
return result;
5457
}

Diff for: src/jetzig/testing/App.zig

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub fn init(allocator: std.mem.Allocator, routes_module: type) !App {
7878

7979
/// Free allocated resources for test app.
8080
pub fn deinit(self: *App) void {
81+
self.repo.deinit();
8182
self.arena.deinit();
8283
self.allocator.destroy(self.arena);
8384
if (self.logger.test_logger.file) |file| file.close();

0 commit comments

Comments
 (0)