Skip to content

Add an example for using auth for logging in #174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
# Create postgres server
# https://github.com/marketplace/actions/setup-postgresql-for-linux-macos-windows
- uses: ikalnytskyi/action-setup-postgres@v7

# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
with:
Expand All @@ -43,7 +47,17 @@ jobs:
- name: Run App Tests
run: |
cd demo
zig build -Denvironment=testing jetzig:database:create
zig build -Denvironment=testing jetzig:database:migrate
zig build -Denvironment=testing jetzig:test
env:
JETQUERY_HOSTNAME: 'localhost'
JETQUERY_USERNAME: 'postgres'
JETQUERY_PASSWORD: 'postgres'
JETQUERY_DATABASE: 'test'
# Assume a small amount of connections are allowed
# into postgres
JETQUERY_POOL_SIZE: 2

- name: Build artifacts
if: ${{ matrix.os == 'ubuntu-latest' }}
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/init.zig
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn run(
try copySourceFile(
allocator,
install_dir,
"demo/config/database.zig",
"demo/config/database_template.zig",
"config/database.zig",
null,
);
Expand Down
2 changes: 1 addition & 1 deletion cli/compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn initDataModule(build: *std.Build) !*std.Build.Module {
"demo/public/zmpl.png",
"demo/public/favicon.ico",
"demo/public/styles.css",
"demo/config/database.zig",
"demo/config/database_template.zig",
".gitignore",
};

Expand Down
47 changes: 3 additions & 44 deletions demo/config/database.zig
Original file line number Diff line number Diff line change
@@ -1,48 +1,7 @@
pub const database = .{
// Null adapter fails when a database call is invoked.
.development = .{
.adapter = .null,
},
// This configuration is used for CI
// in GitHub
.testing = .{
.adapter = .null,
},
.production = .{
.adapter = .null,
.adapter = .postgresql,
},
// PostgreSQL adapter configuration.
//
// All options except `adapter` can be configured using environment variables:
//
// * JETQUERY_HOSTNAME
// * JETQUERY_PORT
// * JETQUERY_USERNAME
// * JETQUERY_PASSWORD
// * JETQUERY_DATABASE
//
// .testing = .{
// .adapter = .postgresql,
// .hostname = "localhost",
// .port = 5432,
// .username = "postgres",
// .password = "password",
// .database = "myapp_testing",
// },
//
// .development = .{
// .adapter = .postgresql,
// .hostname = "localhost",
// .port = 5432,
// .username = "postgres",
// .password = "password",
// .database = "myapp_development",
// },
//
// .production = .{
// .adapter = .postgresql,
// .hostname = "localhost",
// .port = 5432,
// .username = "postgres",
// .password = "password",
// .database = "myapp_production",
// },
};
48 changes: 48 additions & 0 deletions demo/config/database_template.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
pub const database = .{
// Null adapter fails when a database call is invoked.
.development = .{
.adapter = .null,
},
.testing = .{
.adapter = .null,
},
.production = .{
.adapter = .null,
},
// PostgreSQL adapter configuration.
//
// All options except `adapter` can be configured using environment variables:
//
// * JETQUERY_HOSTNAME
// * JETQUERY_PORT
// * JETQUERY_USERNAME
// * JETQUERY_PASSWORD
// * JETQUERY_DATABASE
//
// .testing = .{
// .adapter = .postgresql,
// .hostname = "localhost",
// .port = 5432,
// .username = "postgres",
// .password = "password",
// .database = "myapp_testing",
// },
//
// .development = .{
// .adapter = .postgresql,
// .hostname = "localhost",
// .port = 5432,
// .username = "postgres",
// .password = "password",
// .database = "myapp_development",
// },
//
// .production = .{
// .adapter = .postgresql,
// .hostname = "localhost",
// .port = 5432,
// .username = "postgres",
// .password = "password",
// .database = "myapp_production",
// },
};
9 changes: 9 additions & 0 deletions demo/src/app/database/Schema.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const jetquery = @import("jetzig").jetquery;

pub const User = jetquery.Model(@This(), "users", struct {
id: i32,
email: []const u8,
password_hash: []const u8,
created_at: jetquery.DateTime,
updated_at: jetquery.DateTime,
}, .{});
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const jetquery = @import("jetquery");

pub fn up(repo: *jetquery.Repo) !void {
pub fn up(repo: anytype) !void {
_ = repo;
}

pub fn down(repo: *jetquery.Repo) !void {
pub fn down(repo: anytype) !void {
_ = repo;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const std = @import("std");
const jetquery = @import("jetquery");
const t = jetquery.schema.table;

pub fn up(repo: anytype) !void {
try repo.createTable(
"users",
&.{
t.primaryKey("id", .{}),
t.column("email", .string, .{ .unique = true, .index = true }),
t.column("password_hash", .string, .{}),
t.timestamps(.{}),
},
.{},
);
}

pub fn down(repo: anytype) !void {
try repo.dropTable("users", .{});
}
61 changes: 61 additions & 0 deletions demo/src/app/views/login.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const std = @import("std");
const jetzig = @import("jetzig");
const auth = @import("jetzig").auth;

pub fn index(request: *jetzig.Request) !jetzig.View {
return request.render(.ok);
}

pub fn post(request: *jetzig.Request) !jetzig.View {
const Login = struct {
email: []const u8,
password: []const u8,
};

const params = try request.expectParams(Login) orelse {
return request.fail(.forbidden);
};

// Lookup the user by email
const query = jetzig.database.Query(.User).findBy(
.{ .email = params.email },
);

const user = try request.repo.execute(query) orelse {
return request.fail(.forbidden);
};

// Check that the password matches
if (try auth.verifyPassword(
request.allocator,
user.password_hash,
params.password,
)) {
try auth.signIn(request, user.id);
return request.redirect("/", .found);
}
return request.fail(.forbidden);
}

test "post" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();

const hashed_pass = try auth.hashPassword(std.testing.allocator, "test");
defer std.testing.allocator.free(hashed_pass);

try jetzig.database.Query(.User).deleteAll().execute(app.repo);
try app.repo.insert(.User, .{
.id = 1,
.email = "[email protected]",
.password_hash = hashed_pass,
});

const response = try app.request(.POST, "/login", .{
.json = .{
.email = "[email protected]",
.password = "test",
},
});
try response.expectStatus(.found);
}
7 changes: 7 additions & 0 deletions demo/src/app/views/login/index.zmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<form method="post" id="login">
<input type="email" name="email" placeholder="[email protected]">
<label for="email">Email address</label>
<input type="password" name="password" placeholder="Password">
<label for="password">Password</label>
<button type="submit" form="login">Sign in</button>
</form>
9 changes: 6 additions & 3 deletions src/jetzig/auth.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ pub fn verifyPassword(
}

pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]const u8 {
const buf = try allocator.alloc(u8, 128);
return try std.crypto.pwhash.argon2.strHash(
var buf: [128]u8 = undefined;
const hash = try std.crypto.pwhash.argon2.strHash(
password,
.{
.allocator = allocator,
.params = .{ .t = 3, .m = 32, .p = 4 },
},
buf,
&buf,
);
const result = try allocator.alloc(u8, hash.len);
@memcpy(result, hash);
return result;
}
1 change: 1 addition & 0 deletions src/jetzig/testing/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub fn init(allocator: std.mem.Allocator, routes_module: type) !App {

/// Free allocated resources for test app.
pub fn deinit(self: *App) void {
self.repo.deinit();
self.arena.deinit();
self.allocator.destroy(self.arena);
if (self.logger.test_logger.file) |file| file.close();
Expand Down