102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
package setup
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"github.com/rs/zerolog/log"
|
|
"gitlab.com/texm/shokku/internal/models"
|
|
"gitlab.com/texm/shokku/internal/server/auth"
|
|
"image/png"
|
|
"math/rand"
|
|
"net/http"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/pquerna/otp/totp"
|
|
"gitlab.com/texm/shokku/internal/env"
|
|
"gitlab.com/texm/shokku/internal/server/dto"
|
|
)
|
|
|
|
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
|
|
func generateRandomString(n int) string {
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func GenerateTotp(e *env.Env, c echo.Context) error {
|
|
key, err := totp.Generate(totp.GenerateOpts{
|
|
Issuer: "shokku",
|
|
AccountName: "admin account",
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate totp key: %w", err)
|
|
}
|
|
|
|
var imgBuf bytes.Buffer
|
|
img, imgErr := key.Image(160, 160)
|
|
if imgErr != nil {
|
|
return fmt.Errorf("failed to generate totp image: %w", imgErr)
|
|
}
|
|
if encErr := png.Encode(&imgBuf, img); encErr != nil {
|
|
return fmt.Errorf("failed to encode totp image to png: %w", encErr)
|
|
}
|
|
return c.JSON(http.StatusOK, dto.GenerateTotpResponse{
|
|
Secret: key.Secret(),
|
|
Image: base64.StdEncoding.EncodeToString(imgBuf.Bytes()),
|
|
RecoveryCode: generateRandomString(12),
|
|
})
|
|
}
|
|
|
|
func ConfirmTotp(e *env.Env, c echo.Context) error {
|
|
var req dto.ConfirmTotpRequest
|
|
if err := dto.BindRequest(c, &req); err != nil {
|
|
return err.ToHTTP()
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, dto.ConfirmTotpResponse{
|
|
Valid: totp.Validate(req.Code, req.Secret),
|
|
})
|
|
}
|
|
|
|
func CompletePasswordSetup(e *env.Env, c echo.Context) error {
|
|
var req dto.CompletePasswordSetupRequest
|
|
if err := dto.BindRequest(c, &req); err != nil {
|
|
return err.ToHTTP()
|
|
}
|
|
|
|
if err := setupServerWithAuthMethod(e, auth.MethodPassword); err != nil {
|
|
log.Error().Err(err).Msg("failed to setup github auth")
|
|
return echo.ErrInternalServerError
|
|
}
|
|
|
|
pw, ok := e.Auth.(*auth.PasswordAuthenticator)
|
|
if !ok {
|
|
log.Error().Msg("failed to cast e.Auth to pw auth")
|
|
return echo.ErrInternalServerError
|
|
}
|
|
passwordHash, pwErr := pw.HashPassword([]byte(req.Password))
|
|
if pwErr != nil {
|
|
log.Error().Err(pwErr).Msg("failed to hash password")
|
|
return echo.ErrInternalServerError
|
|
}
|
|
|
|
user := models.User{
|
|
Name: req.Username,
|
|
Source: "manual",
|
|
PasswordHash: passwordHash,
|
|
TotpEnabled: req.Enable2FA,
|
|
RecoveryCode: req.RecoveryCode,
|
|
TotpSecret: req.TotpSecret,
|
|
}
|
|
if err := e.DB.Save(&user).Error; err != nil {
|
|
log.Error().Err(err).Msg("failed to save initial password auth user")
|
|
return echo.ErrInternalServerError
|
|
}
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
}
|