Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
## Description
`veb.auth` is a module that helps with common logic required for authentication.
It allows to easily generate hashed and salted passwords and to compare password hashes.
It also handles authentication tokens, including DB table creation and insertion.
All DBs are supported.
## Usage
```v
import veb
import db.pg
import veb.auth
pub struct App {
veb.StaticHandler
pub mut:
db pg.DB
auth auth.Auth[pg.DB] // or auth.Auth[sqlite.DB] etc
}
const port = 8081
pub struct Context {
veb.Context
current_user User
}
struct User {
id int @[primary; sql: serial]
name string
password_hash string
salt string
}
fn main() {
mut app := &App{
// Use your actual local PostgreSQL password here.
db: pg.connect(host: 'localhost', user: 'postgres', password: 'password', dbname: 'postgres')!
}
app.auth = auth.new(app.db)
veb.run[App, Context](mut app, port)
}
@[post]
pub fn (mut app App) register_user(mut ctx Context, name string, password string) veb.Result {
salt := auth.generate_salt()
new_user := User{
name: name
password_hash: auth.hash_password_with_salt(password, salt)
salt: salt
}
user_id := sql app.db {
insert new_user into User
} or { 0 }
// Generate and insert the token using user ID
token := app.auth.add_token(user_id) or { '' }
// Authenticate the user by adding the token to the cookies
ctx.set_cookie(name: 'token', value: token)
return ctx.redirect('/')
}
@[post]
pub fn (mut app App) login_post(mut ctx Context, name string, password string) veb.Result {
user := app.find_user_by_name(name) or {
ctx.error('Bad credentials')
return ctx.redirect('/login')
}
// Verify user password using veb.auth
if !auth.compare_password_with_hash(password, user.salt, user.password_hash) {
ctx.error('Bad credentials')
return ctx.redirect('/login')
}
// Find the user token in the Token table
token := app.auth.add_token(user.id) or { '' }
// Authenticate the user by adding the token to the cookies
ctx.set_cookie(name: 'token', value: token)
return ctx.redirect('/')
}
pub fn (mut app App) find_user_by_name(name string) ?User {
// ... db query
return User{}
}
```
On macOS, Postgres.app rejects passwordless `trust` connections from unknown
processes. Use a real password in the example above instead of `password: ''`.
## Security considerations
`hash_password_with_salt` and its related functions use `sha256` for hashing with a single
iteration. This is not secure for production use, and you should use a more secure hashing
algorithm and multiple iterations.
See also:
- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
+93
View File
@@ -0,0 +1,93 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module auth
import rand
import crypto.rand as crypto_rand
import crypto.hmac
import crypto.sha256
const max_safe_unsigned_integer = u32(4_294_967_295)
pub struct Auth[T] {
db T
// pub:
// salt string
}
pub struct Token {
pub:
id int @[primary; sql: serial]
user_id int
value string
// ip string
}
pub fn new[T](db T) Auth[T] {
set_rand_crypto_safe_seed()
sql db {
create table Token
} or { eprintln('veb.auth: failed to create table Token') }
return Auth[T]{
db: db
// salt: generate_salt()
}
}
// fn (mut app App) add_token(user_id int, ip string) !string {
pub fn (mut app Auth[T]) add_token(user_id int) !string {
mut uuid := rand.uuid_v4()
token := Token{
user_id: user_id
value: uuid
// ip: ip
}
sql app.db {
insert token into Token
}!
return uuid
}
pub fn (app &Auth[T]) find_token(value string) ?Token {
tokens := sql app.db {
select from Token where value == value limit 1
} or { []Token{} }
if tokens.len == 0 {
return none
}
return tokens.first()
}
pub fn (mut app Auth[T]) delete_tokens(user_id int) ! {
sql app.db {
delete from Token where user_id == user_id
}!
}
pub fn set_rand_crypto_safe_seed() {
first_seed := generate_crypto_safe_int_u32()
second_seed := generate_crypto_safe_int_u32()
rand.seed([first_seed, second_seed])
}
fn generate_crypto_safe_int_u32() u32 {
return u32(crypto_rand.int_u64(max_safe_unsigned_integer) or { 0 })
}
pub fn generate_salt() string {
return rand.i64().str()
}
pub fn hash_password_with_salt(plain_text_password string, salt string) string {
salted_password := '${plain_text_password}${salt}'
return sha256.sum(salted_password.bytes()).hex().str()
}
pub fn compare_password_with_hash(plain_text_password string, salt string, hashed string) bool {
digest := hash_password_with_salt(plain_text_password, salt)
// constant time comparison
// I know this is operating on the hex-encoded strings, but it's still constant time
// and better than not doing it at all
return hmac.equal(digest.bytes(), hashed.bytes())
}
+9
View File
@@ -0,0 +1,9 @@
module auth
pub struct Request {
pub:
client_id string
client_secret string
code string
state string
}