-
-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathtwo_factor_routes.v
More file actions
112 lines (102 loc) · 3.82 KB
/
Copy pathtwo_factor_routes.v
File metadata and controls
112 lines (102 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by a GPL license that can be found in the LICENSE file.
module main
import veb
import time
import crypto.hmac
import crypto.sha256
import encoding.hex
const two_factor_pending_cookie = 'pending_2fa'
const two_factor_pending_ttl = 300 // seconds
fn (mut app App) pending_2fa_key(user User) []u8 {
return '${user.password}:${user.salt}'.bytes()
}
fn (mut app App) sign_pending_2fa(user User, expires i64) string {
payload := '${user.id}:${expires}'
mac := hmac.new(app.pending_2fa_key(user), payload.bytes(), sha256.sum, sha256.block_size)
return '${payload}:${hex.encode(mac)}'
}
fn (mut app App) verify_pending_2fa(token string) ?User {
parts := token.split(':')
if parts.len != 3 {
return none
}
user_id := parts[0].int()
expires := parts[1].i64()
sig := parts[2]
if expires < time.now().unix() {
return none
}
user := app.get_user_by_id(user_id) or { return none }
payload := '${user_id}:${expires}'
expected := hmac.new(app.pending_2fa_key(user), payload.bytes(), sha256.sum, sha256.block_size)
if hex.encode(expected) != sig {
return none
}
return user
}
@['/login/2fa']
pub fn (mut app App) two_factor_prompt(mut ctx Context) veb.Result {
pending := ctx.get_cookie(two_factor_pending_cookie) or { return ctx.redirect_to_login() }
app.verify_pending_2fa(pending) or { return ctx.redirect_to_login() }
return $veb.html('templates/two_factor_login.html')
}
@['/login/2fa'; post]
pub fn (mut app App) handle_two_factor_login(mut ctx Context, code string) veb.Result {
pending := ctx.get_cookie(two_factor_pending_cookie) or { return ctx.redirect_to_login() }
user := app.verify_pending_2fa(pending) or { return ctx.redirect_to_login() }
tf := app.find_two_factor(user.id) or { return ctx.redirect_to_login() }
if !tf.is_enabled || !verify_totp(tf.secret, code.trim_space()) {
ctx.error('Invalid verification code')
return $veb.html('templates/two_factor_login.html')
}
ctx.set_cookie(name: two_factor_pending_cookie, value: '')
app.auth_user(mut ctx, user, ctx.ip()) or {
ctx.error('There was an error while logging in')
return ctx.redirect_to_login()
}
app.add_security_log(user_id: user.id, kind: .logged_in) or { app.info(err.str()) }
return ctx.redirect('/${user.username}')
}
@['/:username/settings/2fa']
pub fn (mut app App) view_two_factor_settings(mut ctx Context, username string) veb.Result {
if !ctx.logged_in || ctx.user.username != username {
return ctx.redirect_to_index()
}
tf := app.find_two_factor(ctx.user.id) or {
TwoFactor{
user_id: ctx.user.id
}
}
enabled := tf.is_enabled
mut secret := ''
mut provisioning_uri := ''
if !enabled {
secret = if tf.secret == '' { generate_totp_secret() } else { tf.secret }
app.upsert_two_factor(ctx.user.id, secret, false) or {}
provisioning_uri = totp_provisioning_uri(username, secret)
}
return $veb.html('templates/two_factor_settings.html')
}
@['/:username/settings/2fa/enable'; post]
pub fn (mut app App) handle_enable_two_factor(mut ctx Context, username string) veb.Result {
if !ctx.logged_in || ctx.user.username != username {
return ctx.redirect_to_index()
}
code := ctx.form['code'].trim_space()
tf := app.find_two_factor(ctx.user.id) or { return ctx.redirect('/${username}/settings/2fa') }
if !verify_totp(tf.secret, code) {
ctx.error('Invalid verification code')
return ctx.redirect('/${username}/settings/2fa')
}
app.upsert_two_factor(ctx.user.id, tf.secret, true) or {}
return ctx.redirect('/${username}/settings/2fa')
}
@['/:username/settings/2fa/disable'; post]
pub fn (mut app App) handle_disable_two_factor(mut ctx Context, username string) veb.Result {
if !ctx.logged_in || ctx.user.username != username {
return ctx.redirect_to_index()
}
app.delete_two_factor(ctx.user.id) or {}
return ctx.redirect('/${username}/settings/2fa')
}