v2 / vlib / veb / auth / auth.v
93 lines · 80 sloc · 2.16 KB · e3b915dac183902833f2358a6fb76c46d07a52c3
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module auth
5
6import rand
7import crypto.rand as crypto_rand
8import crypto.hmac
9import crypto.sha256
10
11const max_safe_unsigned_integer = u32(4_294_967_295)
12
13pub struct Auth[T] {
14 db T
15 // pub:
16 // salt string
17}
18
19pub struct Token {
20pub:
21 id int @[primary; sql: serial]
22 user_id int
23 value string
24 // ip string
25}
26
27pub fn new[T](db T) Auth[T] {
28 set_rand_crypto_safe_seed()
29 sql db {
30 create table Token
31 } or { eprintln('veb.auth: failed to create table Token') }
32 return Auth[T]{
33 db: db
34 // salt: generate_salt()
35 }
36}
37
38// fn (mut app App) add_token(user_id int, ip string) !string {
39pub fn (mut app Auth[T]) add_token(user_id int) !string {
40 mut uuid := rand.uuid_v4()
41 token := Token{
42 user_id: user_id
43 value: uuid
44 // ip: ip
45 }
46 sql app.db {
47 insert token into Token
48 }!
49 return uuid
50}
51
52pub fn (app &Auth[T]) find_token(value string) ?Token {
53 tokens := sql app.db {
54 select from Token where value == value limit 1
55 } or { []Token{} }
56 if tokens.len == 0 {
57 return none
58 }
59 return tokens.first()
60}
61
62pub fn (mut app Auth[T]) delete_tokens(user_id int) ! {
63 sql app.db {
64 delete from Token where user_id == user_id
65 }!
66}
67
68pub fn set_rand_crypto_safe_seed() {
69 first_seed := generate_crypto_safe_int_u32()
70 second_seed := generate_crypto_safe_int_u32()
71 rand.seed([first_seed, second_seed])
72}
73
74fn generate_crypto_safe_int_u32() u32 {
75 return u32(crypto_rand.int_u64(max_safe_unsigned_integer) or { 0 })
76}
77
78pub fn generate_salt() string {
79 return rand.i64().str()
80}
81
82pub fn hash_password_with_salt(plain_text_password string, salt string) string {
83 salted_password := '${plain_text_password}${salt}'
84 return sha256.sum(salted_password.bytes()).hex().str()
85}
86
87pub fn compare_password_with_hash(plain_text_password string, salt string, hashed string) bool {
88 digest := hash_password_with_salt(plain_text_password, salt)
89 // constant time comparison
90 // I know this is operating on the hex-encoded strings, but it's still constant time
91 // and better than not doing it at all
92 return hmac.equal(digest.bytes(), hashed.bytes())
93}
94