a mini social media app for small communities
1// From: https://github.com/vlang/v/blob/1fae506900c79e3aafc00e08e1f861fc7cbf8012/vlib/veb/auth/auth.v 2// The original file's source is licensed under MIT. 3 4// This fork re-introduces the `ip` field of each token for additional security, 5// along with delete_tokens_for_ip 6 7module auth 8 9import rand 10import crypto.rand as crypto_rand 11import crypto.hmac 12import crypto.sha256 13 14const max_safe_unsigned_integer = u32(4_294_967_295) 15 16pub struct Auth[T] { 17 db T 18} 19 20pub struct Token { 21pub: 22 id int @[primary; sql: serial] 23 user_id int 24 value string 25 ip string 26} 27 28pub fn new[T](db T) Auth[T] { 29 set_rand_crypto_safe_seed() 30 sql db { 31 create table Token 32 } or { eprintln('veb.auth: failed to create table Token') } 33 return Auth[T]{ 34 db: db 35 } 36} 37 38pub fn (mut app Auth[T]) add_token(user_id int, ip string) !string { 39 mut uuid := rand.uuid_v4() 40 token := Token{ 41 user_id: user_id 42 value: uuid 43 ip: ip 44 } 45 sql app.db { 46 insert token into Token 47 }! 48 return uuid 49} 50 51pub fn (app &Auth[T]) find_token(value string, ip string) ?Token { 52 tokens := sql app.db { 53 select from Token where value == value && ip == ip limit 1 54 } or { []Token{} } 55 if tokens.len == 0 { 56 return none 57 } 58 return tokens.first() 59} 60 61pub fn (mut app Auth[T]) delete_tokens_for_user(user_id int) ! { 62 sql app.db { 63 delete from Token where user_id == user_id 64 }! 65} 66 67pub fn (mut app Auth[T]) delete_tokens_for_ip(ip string) ! { 68 sql app.db { 69 delete from Token where ip == ip 70 }! 71} 72 73pub fn set_rand_crypto_safe_seed() { 74 first_seed := generate_crypto_safe_int_u32() 75 second_seed := generate_crypto_safe_int_u32() 76 rand.seed([first_seed, second_seed]) 77} 78 79fn generate_crypto_safe_int_u32() u32 { 80 return u32(crypto_rand.int_u64(max_safe_unsigned_integer) or { 0 }) 81} 82 83pub fn generate_salt() string { 84 return rand.i64().str() 85} 86 87pub fn hash_password_with_salt(plain_text_password string, salt string) string { 88 salted_password := '${plain_text_password}${salt}' 89 return sha256.sum(salted_password.bytes()).hex().str() 90} 91 92pub fn compare_password_with_hash(plain_text_password string, salt string, hashed string) bool { 93 digest := hash_password_with_salt(plain_text_password, salt) 94 // constant time comparison 95 // I know this is operating on the hex-encoded strings, but it's still constant time 96 // and better than not doing it at all 97 return hmac.equal(digest.bytes(), hashed.bytes()) 98}