a mini social media app for small communities
at main 2.6 kB view raw
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// ~~ 5// This fork re-introduces the `ip` field of each token for additional security, 6// along with delete_tokens_for_ip 7// ~~ 8// IP has been removed since IPs can change randomly and it causes you to need 9// to relog wayyyy more often. I'm keeping this fork just in case I do need to 10// change the auth system in the future. 11 12module auth 13 14import rand 15import crypto.rand as crypto_rand 16import crypto.hmac 17import crypto.sha256 18 19const max_safe_unsigned_integer = u32(4_294_967_295) 20 21pub struct Auth[T] { 22 db T 23} 24 25pub struct Token { 26pub: 27 id int @[primary; sql: serial] 28 user_id int 29 value string 30} 31 32pub fn new[T](db T) Auth[T] { 33 set_rand_crypto_safe_seed() 34 sql db { 35 create table Token 36 } or { eprintln('veb.auth: failed to create table Token') } 37 return Auth[T]{ 38 db: db 39 } 40} 41 42pub fn (mut app Auth[T]) add_token(user_id int) !string { 43 mut uuid := rand.uuid_v4() 44 token := Token{ 45 user_id: user_id 46 value: uuid 47 } 48 sql app.db { 49 insert token into Token 50 }! 51 return uuid 52} 53 54pub fn (app &Auth[T]) find_token(value string) ?Token { 55 tokens := sql app.db { 56 select from Token where value == value limit 1 57 } or { []Token{} } 58 if tokens.len == 0 { 59 return none 60 } 61 return tokens.first() 62} 63 64// logs out of all devices 65pub fn (mut app Auth[T]) delete_tokens_for_user(user_id int) ! { 66 sql app.db { 67 delete from Token where user_id == user_id 68 }! 69} 70 71// logs out of one device 72pub fn (mut app Auth[T]) delete_tokens_for_value(value string) ! { 73 sql app.db { 74 delete from Token where value == value 75 }! 76} 77 78pub fn set_rand_crypto_safe_seed() { 79 first_seed := generate_crypto_safe_int_u32() 80 second_seed := generate_crypto_safe_int_u32() 81 rand.seed([first_seed, second_seed]) 82} 83 84fn generate_crypto_safe_int_u32() u32 { 85 return u32(crypto_rand.int_u64(max_safe_unsigned_integer) or { 0 }) 86} 87 88pub fn generate_salt() string { 89 return rand.i64().str() 90} 91 92pub fn hash_password_with_salt(plain_text_password string, salt string) string { 93 salted_password := '${plain_text_password}${salt}' 94 return sha256.sum(salted_password.bytes()).hex().str() 95} 96 97pub fn compare_password_with_hash(plain_text_password string, salt string, hashed string) bool { 98 digest := hash_password_with_salt(plain_text_password, salt) 99 // constant time comparison 100 // I know this is operating on the hex-encoded strings, but it's still constant time 101 // and better than not doing it at all 102 return hmac.equal(digest.bytes(), hashed.bytes()) 103}