mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 05:56:07 +00:00
Merge pull request #174 from sc68cal/login_example
Add an example for using auth for logging in
This commit is contained in:
commit
2c52792217
14
.github/workflows/CI.yml
vendored
14
.github/workflows/CI.yml
vendored
@ -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:
|
||||
@ -40,7 +44,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' }}
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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",
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
.adapter = .postgresql,
|
||||
},
|
||||
.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",
|
||||
// },
|
||||
};
|
||||
|
48
demo/config/database_template.zig
Normal file
48
demo/config/database_template.zig
Normal file
@ -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
demo/src/app/database/Schema.zig
Normal file
9
demo/src/app/database/Schema.zig
Normal file
@ -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,
|
||||
}, .{});
|
@ -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;
|
||||
}
|
||||
|
@ -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
demo/src/app/views/login.zig
Normal file
61
demo/src/app/views/login.zig
Normal file
@ -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 = "test@test.com",
|
||||
.password_hash = hashed_pass,
|
||||
});
|
||||
|
||||
const response = try app.request(.POST, "/login", .{
|
||||
.json = .{
|
||||
.email = "test@test.com",
|
||||
.password = "test",
|
||||
},
|
||||
});
|
||||
try response.expectStatus(.found);
|
||||
}
|
7
demo/src/app/views/login/index.zmpl
Normal file
7
demo/src/app/views/login/index.zmpl
Normal file
@ -0,0 +1,7 @@
|
||||
<form method="post" id="login">
|
||||
<input type="email" name="email" placeholder="name@example.com">
|
||||
<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>
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user