mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +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]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
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
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@ -40,7 +44,17 @@ jobs:
|
|||||||
- name: Run App Tests
|
- name: Run App Tests
|
||||||
run: |
|
run: |
|
||||||
cd demo
|
cd demo
|
||||||
|
zig build -Denvironment=testing jetzig:database:create
|
||||||
|
zig build -Denvironment=testing jetzig:database:migrate
|
||||||
zig build -Denvironment=testing jetzig:test
|
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
|
- name: Build artifacts
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||||
|
@ -106,7 +106,7 @@ pub fn run(
|
|||||||
try copySourceFile(
|
try copySourceFile(
|
||||||
allocator,
|
allocator,
|
||||||
install_dir,
|
install_dir,
|
||||||
"demo/config/database.zig",
|
"demo/config/database_template.zig",
|
||||||
"config/database.zig",
|
"config/database.zig",
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
@ -30,7 +30,7 @@ pub fn initDataModule(build: *std.Build) !*std.Build.Module {
|
|||||||
"demo/public/zmpl.png",
|
"demo/public/zmpl.png",
|
||||||
"demo/public/favicon.ico",
|
"demo/public/favicon.ico",
|
||||||
"demo/public/styles.css",
|
"demo/public/styles.css",
|
||||||
"demo/config/database.zig",
|
"demo/config/database_template.zig",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,48 +1,7 @@
|
|||||||
pub const database = .{
|
pub const database = .{
|
||||||
// Null adapter fails when a database call is invoked.
|
// This configuration is used for CI
|
||||||
.development = .{
|
// in GitHub
|
||||||
.adapter = .null,
|
|
||||||
},
|
|
||||||
.testing = .{
|
.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");
|
const jetquery = @import("jetquery");
|
||||||
|
|
||||||
pub fn up(repo: *jetquery.Repo) !void {
|
pub fn up(repo: anytype) !void {
|
||||||
_ = repo;
|
_ = repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn down(repo: *jetquery.Repo) !void {
|
pub fn down(repo: anytype) !void {
|
||||||
_ = repo;
|
_ = 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 {
|
pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]const u8 {
|
||||||
const buf = try allocator.alloc(u8, 128);
|
var buf: [128]u8 = undefined;
|
||||||
return try std.crypto.pwhash.argon2.strHash(
|
const hash = try std.crypto.pwhash.argon2.strHash(
|
||||||
password,
|
password,
|
||||||
.{
|
.{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.params = .{ .t = 3, .m = 32, .p = 4 },
|
.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.
|
/// Free allocated resources for test app.
|
||||||
pub fn deinit(self: *App) void {
|
pub fn deinit(self: *App) void {
|
||||||
|
self.repo.deinit();
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.allocator.destroy(self.arena);
|
self.allocator.destroy(self.arena);
|
||||||
if (self.logger.test_logger.file) |file| file.close();
|
if (self.logger.test_logger.file) |file| file.close();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user