TOTP fully functioning, account settings done!

This commit is contained in:
ari melody 2025-01-23 13:53:06 +00:00
parent 50cbce92fc
commit e004491b55
Signed by: ari
GPG key ID: CF99829C92678188
11 changed files with 143 additions and 48 deletions

View file

@ -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

View file

@ -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)

View file

@ -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;

View file

@ -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"),

View file

@ -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}}

View 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}}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 {