Files
dokku-ui/internal/server/api/setup/password.go
2023-04-25 14:33:14 +08:00

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