TOTP fully functioning, account settings done!
This commit is contained in:
parent
50cbce92fc
commit
e004491b55
|
@ -30,15 +30,30 @@ func accountIndexHandler(app *model.AppState) http.Handler {
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
totps, err := controller.GetTOTPsForAccount(app.DB, session.Account.ID)
|
||||
dbTOTPs, err := controller.GetTOTPsForAccount(app.DB, session.Account.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
type accountResponse struct {
|
||||
type (
|
||||
TOTP struct {
|
||||
model.TOTP
|
||||
CreatedAtString string
|
||||
}
|
||||
|
||||
accountResponse struct {
|
||||
Session *model.Session
|
||||
TOTPs []model.TOTP
|
||||
TOTPs []TOTP
|
||||
}
|
||||
)
|
||||
|
||||
totps := []TOTP{}
|
||||
for _, totp := range dbTOTPs {
|
||||
totps = append(totps, TOTP{
|
||||
TOTP: totp,
|
||||
CreatedAtString: totp.CreatedAt.Format("02 Jan 2006, 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
sessionMessage := session.Message
|
||||
|
|
|
@ -284,7 +284,36 @@ func loginHandler(app *model.AppState) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
totpMethod, err := controller.CheckTOTPForAccount(app.DB, account.ID, credentials.TOTP)
|
||||
var totpMethod *model.TOTP
|
||||
if len(credentials.TOTP) == 0 {
|
||||
// check if user has TOTP
|
||||
totps, err := controller.GetTOTPsForAccount(app.DB, account.ID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err)
|
||||
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||
render()
|
||||
return
|
||||
}
|
||||
|
||||
if len(totps) > 0 {
|
||||
type loginTOTPData struct {
|
||||
Session *model.Session
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
err = pages["login-totp"].Execute(w, loginTOTPData{
|
||||
Session: session,
|
||||
Username: credentials.Username,
|
||||
Password: credentials.Password,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to render login TOTP page: %v\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
totpMethod, err = controller.CheckTOTPForAccount(app.DB, account.ID, credentials.TOTP)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err)
|
||||
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||
|
@ -296,14 +325,24 @@ func loginHandler(app *model.AppState) http.Handler {
|
|||
render()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: log login activity to user
|
||||
if totpMethod != nil {
|
||||
fmt.Printf(
|
||||
"[%s] INFO: Account \"%s\" logged in with method \"%s\"\n",
|
||||
time.Now().Format(time.UnixDate),
|
||||
account.Username,
|
||||
totpMethod.Name,
|
||||
)
|
||||
} else {
|
||||
fmt.Printf(
|
||||
"[%s] INFO: Account \"%s\" logged in\n",
|
||||
time.Now().Format(time.UnixDate),
|
||||
account.Username,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: log login activity to user
|
||||
|
||||
// login success!
|
||||
controller.SetSessionAccount(app.DB, session, account)
|
||||
|
|
|
@ -85,6 +85,15 @@ a img.icon {
|
|||
height: .8em;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #303030;
|
||||
color: #f0f0f0;
|
||||
padding: .23em .3em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.card {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
@ -93,13 +102,6 @@ a img.icon {
|
|||
margin: 0 0 .5em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
.card h3,
|
||||
.card p {
|
||||
margin: 0;
|
||||
}
|
||||
*/
|
||||
|
||||
.card-title {
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
|
|
|
@ -18,6 +18,11 @@ var pages = map[string]*template.Template{
|
|||
filepath.Join("views", "prideflag.html"),
|
||||
filepath.Join("admin", "views", "login.html"),
|
||||
)),
|
||||
"login-totp": template.Must(template.ParseFiles(
|
||||
filepath.Join("admin", "views", "layout.html"),
|
||||
filepath.Join("views", "prideflag.html"),
|
||||
filepath.Join("admin", "views", "login-totp.html"),
|
||||
)),
|
||||
"register": template.Must(template.ParseFiles(
|
||||
filepath.Join("admin", "views", "layout.html"),
|
||||
filepath.Join("views", "prideflag.html"),
|
||||
|
|
|
@ -40,11 +40,11 @@
|
|||
{{range .TOTPs}}
|
||||
<div class="mfa-device">
|
||||
<div>
|
||||
<p class="mfa-device-name">{{.Name}}</p>
|
||||
<p class="mfa-device-date">Added: {{.CreatedAt}}</p>
|
||||
<p class="mfa-device-name">{{.TOTP.Name}}</p>
|
||||
<p class="mfa-device-date">Added: {{.CreatedAtString}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a class="button delete" href="/admin/account/totp-delete/{{.Name}}">Delete</a>
|
||||
<a class="button delete" href="/admin/account/totp-delete/{{.TOTP.Name}}">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
42
admin/views/login-totp.html
Normal file
42
admin/views/login-totp.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
{{define "head"}}
|
||||
<title>Login - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<style>
|
||||
form#login-totp {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form div {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
form button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: calc(100% - 1rem - 2px);
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<form action="/admin/login" method="POST" id="login-totp">
|
||||
<h1>Two-Factor Authentication</h1>
|
||||
|
||||
<div>
|
||||
<label for="totp">TOTP</label>
|
||||
<input type="text" name="totp" value="" autocomplete="one-time-code" required autofocus>
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<input type="hidden" name="password" value="{{.Password}}">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="save">Login</button>
|
||||
</form>
|
||||
</main>
|
||||
{{end}}
|
|
@ -3,14 +3,6 @@
|
|||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<style>
|
||||
p a {
|
||||
color: #2a67c8;
|
||||
}
|
||||
|
||||
a.discord {
|
||||
color: #5865F2;
|
||||
}
|
||||
|
||||
form#login {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
@ -27,7 +19,7 @@ form button {
|
|||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
width: calc(100% - 1rem - 2px);
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
@ -42,15 +34,14 @@ input {
|
|||
{{end}}
|
||||
|
||||
<form action="/admin/login" method="POST" id="login">
|
||||
<h1>Log In</h1>
|
||||
|
||||
<div>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" value="" autocomplete="username" required>
|
||||
<input type="text" name="username" value="" autocomplete="username" required autofocus>
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" value="" autocomplete="current-password" required>
|
||||
|
||||
<label for="totp">TOTP</label>
|
||||
<input type="text" name="totp" value="" autocomplete="one-time-code" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="save">Login</button>
|
||||
|
|
|
@ -27,7 +27,7 @@ form button {
|
|||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
width: calc(100% - 1rem - 2px);
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
@ -39,9 +39,11 @@ input {
|
|||
{{end}}
|
||||
|
||||
<form action="/admin/register" method="POST" id="register">
|
||||
<h1>Create Account</h1>
|
||||
|
||||
<div>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" value="" autocomplete="username" required>
|
||||
<input type="text" name="username" value="" autocomplete="username" required autofocus>
|
||||
|
||||
<label for="email">Email</label>
|
||||
<input type="text" name="email" value="" autocomplete="email" required>
|
||||
|
|
|
@ -26,7 +26,7 @@ code {
|
|||
</p>
|
||||
|
||||
<label for="totp">TOTP:</label>
|
||||
<input type="text" name="totp" value="" autocomplete="one-time-code" required>
|
||||
<input type="text" name="totp" value="" autocomplete="one-time-code" required autofocus>
|
||||
|
||||
<button type="submit" class="new">Create</button>
|
||||
</form>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<form action="/admin/account/totp-setup" method="POST" id="totp-setup">
|
||||
<label for="totp-name">TOTP Device Name:</label>
|
||||
<input type="text" name="totp-name" value="" autocomplete="off" required>
|
||||
<input type="text" name="totp-name" value="" autocomplete="off" required autofocus>
|
||||
|
||||
<button type="submit" class="new">Create</button>
|
||||
</form>
|
||||
|
|
|
@ -92,7 +92,6 @@ func GetTOTPsForAccount(db *sqlx.DB, accountID string) ([]model.TOTP, error) {
|
|||
func CheckTOTPForAccount(db *sqlx.DB, accountID string, totp string) (*model.TOTP, error) {
|
||||
totps, err := GetTOTPsForAccount(db, accountID)
|
||||
if err != nil {
|
||||
// user has no TOTP methods
|
||||
return nil, err
|
||||
}
|
||||
for _, method := range totps {
|
||||
|
|
Loading…
Reference in a new issue