|
|
|
@ -1,486 +1,595 @@
|
|
|
|
|
package account_server
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/api"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/mjwt"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/mjwt/auth"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/mjwt/flow"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/utils"
|
|
|
|
|
"crypto"
|
|
|
|
|
"crypto/subtle"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
|
"github.com/sec51/twofactor"
|
|
|
|
|
"net/http"
|
|
|
|
|
"regexp"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
"xorm.io/xorm"
|
|
|
|
|
"bytes"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/api"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/mjwt"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/mjwt/auth"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/mjwt/flow"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
|
|
|
|
"code.mrmelon54.com/melon/summer/pkg/utils"
|
|
|
|
|
"crypto"
|
|
|
|
|
"crypto/subtle"
|
|
|
|
|
_ "embed"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/emersion/go-sasl"
|
|
|
|
|
"github.com/emersion/go-smtp"
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
|
"github.com/sec51/twofactor"
|
|
|
|
|
"html/template"
|
|
|
|
|
"net/http"
|
|
|
|
|
"regexp"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
"xorm.io/xorm"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
emailPattern = regexp.MustCompile("^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,}$")
|
|
|
|
|
mfaCodePattern = regexp.MustCompile("^\\d{6,8}$")
|
|
|
|
|
emailCodePattern = regexp.MustCompile("^\\d{10}$")
|
|
|
|
|
emailPattern = regexp.MustCompile("^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,}$")
|
|
|
|
|
usernamePattern = regexp.MustCompile("^[\\w-_.]{3,32}$")
|
|
|
|
|
mfaCodePattern = regexp.MustCompile("^\\d{6,8}$")
|
|
|
|
|
emailCodePattern = regexp.MustCompile("^\\d{10}$")
|
|
|
|
|
|
|
|
|
|
//go:embed email-template.go.html
|
|
|
|
|
emailTemplate string
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AccountServer struct {
|
|
|
|
|
signer mjwt.Provider
|
|
|
|
|
router *mux.Router
|
|
|
|
|
db *xorm.Engine
|
|
|
|
|
flows map[string]*LoginFlow
|
|
|
|
|
mFlow *sync.RWMutex
|
|
|
|
|
signer mjwt.Provider
|
|
|
|
|
router *mux.Router
|
|
|
|
|
db *xorm.Engine
|
|
|
|
|
flows map[string]*LoginFlow
|
|
|
|
|
mFlow *sync.RWMutex
|
|
|
|
|
smtp SmtpConfig
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewAccountServer(router *mux.Router, db *xorm.Engine, signer mjwt.Provider) *AccountServer {
|
|
|
|
|
s := &AccountServer{
|
|
|
|
|
signer: signer,
|
|
|
|
|
router: router,
|
|
|
|
|
db: db,
|
|
|
|
|
flows: make(map[string]*LoginFlow),
|
|
|
|
|
mFlow: &sync.RWMutex{},
|
|
|
|
|
}
|
|
|
|
|
router.HandleFunc("/login", s.handlePostLogin).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/refresh", s.handlePostRefresh).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/password-reset", s.handlePostPasswordReset).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/password-email", s.handlePostPasswordEmail).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/mfa", s.handleMfaCreate).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/mfa-disable", s.handleMfaDisable).Methods(http.MethodPost)
|
|
|
|
|
return s
|
|
|
|
|
func NewAccountServer(router *mux.Router, db *xorm.Engine, signer mjwt.Provider, smtpConfig SmtpConfig) *AccountServer {
|
|
|
|
|
s := &AccountServer{
|
|
|
|
|
signer: signer,
|
|
|
|
|
router: router,
|
|
|
|
|
db: db,
|
|
|
|
|
flows: make(map[string]*LoginFlow),
|
|
|
|
|
mFlow: &sync.RWMutex{},
|
|
|
|
|
smtp: smtpConfig,
|
|
|
|
|
}
|
|
|
|
|
router.HandleFunc("/login", s.handlePostLogin).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/refresh", s.handlePostRefresh).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/password-reset", s.handlePostPasswordReset).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/password-email", s.handlePostPasswordEmail).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/mfa", s.handleMfaCreate).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/mfa-disable", s.handleMfaDisable).Methods(http.MethodPost)
|
|
|
|
|
router.HandleFunc("/register", s.handlePostRegister).Methods(http.MethodPost)
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) LoadMfaTotp(u *user.User) (*twofactor.Totp, error) {
|
|
|
|
|
if u.MfaObj != nil {
|
|
|
|
|
return u.MfaObj, nil
|
|
|
|
|
}
|
|
|
|
|
otp, err := twofactor.TOTPFromBytes(u.MfaBytes, a.signer.Issuer())
|
|
|
|
|
u.MfaObj = otp
|
|
|
|
|
return u.MfaObj, err
|
|
|
|
|
if u.MfaObj != nil {
|
|
|
|
|
return u.MfaObj, nil
|
|
|
|
|
}
|
|
|
|
|
otp, err := twofactor.TOTPFromBytes(u.MfaBytes, a.signer.Issuer())
|
|
|
|
|
u.MfaObj = otp
|
|
|
|
|
return u.MfaObj, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) outputUserTokens(rw http.ResponseWriter, u *user.User) {
|
|
|
|
|
// Get perms list
|
|
|
|
|
var perms []user.ExtendUserPerm
|
|
|
|
|
err := a.db.Table(&user.LinkUserPerm{}).Where("user_id = ?", u.Id).Join("INNER", &user.Perm{}, "link_user_perm.perm_id = perm.id").Find(&perms)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Internal Server Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Get perms list
|
|
|
|
|
var perms []user.ExtendUserPerm
|
|
|
|
|
err := a.db.Table(&user.LinkUserPerm{}).Where("user_id = ?", u.Id).Join("INNER", &user.Perm{}, "link_user_perm.perm_id = perm.id").Find(&perms)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Internal Server Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
storage := mjwt.NewPermStorage()
|
|
|
|
|
for _, i := range perms {
|
|
|
|
|
storage.Set(i.Perm.Name)
|
|
|
|
|
}
|
|
|
|
|
storage := mjwt.NewPermStorage()
|
|
|
|
|
for _, i := range perms {
|
|
|
|
|
storage.Set(i.Perm.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate access and refresh tokens
|
|
|
|
|
accessToken, err := auth.CreateAccessToken(a.signer, u.Email, uuid.NewString(), u.Id, storage)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
refreshToken, err := auth.CreateRefreshToken(a.signer, u.Email, uuid.NewString(), u.Id)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Generate access and refresh tokens
|
|
|
|
|
accessToken, err := auth.CreateAccessToken(a.signer, u.Email, uuid.NewString(), u.Id, storage)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
refreshToken, err := auth.CreateRefreshToken(a.signer, u.Email, uuid.NewString(), u.Id)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Respond
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
|
rw.WriteHeader(http.StatusAccepted)
|
|
|
|
|
_ = json.NewEncoder(rw).Encode(LoginSuccessResp{
|
|
|
|
|
AccessToken: accessToken,
|
|
|
|
|
RefreshToken: refreshToken,
|
|
|
|
|
})
|
|
|
|
|
// Respond
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
|
rw.WriteHeader(http.StatusAccepted)
|
|
|
|
|
_ = json.NewEncoder(rw).Encode(LoginSuccessResp{
|
|
|
|
|
AccessToken: accessToken,
|
|
|
|
|
RefreshToken: refreshToken,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) outputFlowStep(rw http.ResponseWriter, sub, nextStep string, loginId uint64) {
|
|
|
|
|
flowToken, err := flow.CreateLoginFlowToken(a.signer, sub, uuid.NewString(), nextStep, loginId, time.Minute*10)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
|
rw.WriteHeader(http.StatusOK)
|
|
|
|
|
_ = json.NewEncoder(rw).Encode(LoginFlowResp{Next: nextStep, Token: flowToken})
|
|
|
|
|
flowToken, err := flow.CreateLoginFlowToken(a.signer, sub, uuid.NewString(), nextStep, loginId, time.Minute*10)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
|
rw.WriteHeader(http.StatusOK)
|
|
|
|
|
_ = json.NewEncoder(rw).Encode(LoginFlowResp{Next: nextStep, Token: flowToken})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handlePostLogin(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
u := uuid.NewString()
|
|
|
|
|
f := &LoginFlow{User: nil}
|
|
|
|
|
a.mFlow.Lock()
|
|
|
|
|
a.flows[u] = f
|
|
|
|
|
a.mFlow.Unlock()
|
|
|
|
|
a.outputFlowStep(rw, u, "login", 0)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
u := uuid.NewString()
|
|
|
|
|
f := &LoginFlow{User: nil}
|
|
|
|
|
a.mFlow.Lock()
|
|
|
|
|
a.flows[u] = f
|
|
|
|
|
a.mFlow.Unlock()
|
|
|
|
|
a.outputFlowStep(rw, u, "login", 0)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify mjwt
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[flow.LoginFlowClaims](a.signer, token)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, z, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Verify mjwt
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[flow.LoginFlowClaims](a.signer, token)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, z, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The request was sent to verify an existing access token and was successful
|
|
|
|
|
a.outputFlowStep(rw, z.ID, "done", z.Claims.UserId)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// The request was sent to verify an existing access token and was successful
|
|
|
|
|
a.outputFlowStep(rw, z.ID, "done", z.Claims.UserId)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch flow user
|
|
|
|
|
a.mFlow.RLock()
|
|
|
|
|
u, ok := a.flows[b.Subject]
|
|
|
|
|
a.mFlow.RUnlock()
|
|
|
|
|
if !ok {
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusForbidden, "Login Process Not Started") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Fetch flow user
|
|
|
|
|
a.mFlow.RLock()
|
|
|
|
|
u, ok := a.flows[b.Subject]
|
|
|
|
|
a.mFlow.RUnlock()
|
|
|
|
|
if !ok {
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusForbidden, "Login Process Not Started") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get input
|
|
|
|
|
var in LoginFlowInput
|
|
|
|
|
err = json.NewDecoder(req.Body).Decode(&in)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Get input
|
|
|
|
|
var in LoginFlowInput
|
|
|
|
|
err = json.NewDecoder(req.Body).Decode(&in)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle next state
|
|
|
|
|
switch b.Claims.NextState {
|
|
|
|
|
case "login":
|
|
|
|
|
// Check email matches pattern
|
|
|
|
|
if !emailPattern.MatchString(in.Email) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid email input")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check password matches pattern
|
|
|
|
|
if !ValidatePassword(in.Password) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid password input")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Handle next state
|
|
|
|
|
switch b.Claims.NextState {
|
|
|
|
|
case "login":
|
|
|
|
|
// Check email matches pattern
|
|
|
|
|
if !emailPattern.MatchString(in.Email) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid email input")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check password matches pattern
|
|
|
|
|
if !ValidatePassword(in.Password) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid password input")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find matching emails
|
|
|
|
|
var users []user.User
|
|
|
|
|
err := a.db.Where("email = ?", in.Email).Find(&users)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Internal Server Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Find matching emails
|
|
|
|
|
var users []user.User
|
|
|
|
|
err := a.db.Where("email = ?", in.Email).Find(&users)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Internal Server Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find specific user
|
|
|
|
|
if len(users) < 1 {
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Login Invalid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if len(users) > 1 {
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Internal Server Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
u.User = &users[0]
|
|
|
|
|
// Find specific user
|
|
|
|
|
if len(users) < 1 {
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Login Invalid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if len(users) > 1 {
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Internal Server Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
u.User = &users[0]
|
|
|
|
|
|
|
|
|
|
// Check password
|
|
|
|
|
err = CheckPasswordHash(u.User.Password, in.Password)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Login Invalid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check password
|
|
|
|
|
err = CheckPasswordHash(u.User.Password, in.Password)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Login Invalid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check email
|
|
|
|
|
if !utils.SBool(u.User.EmailVerified) {
|
|
|
|
|
a.outputFlowStep(rw, b.Subject, "email", u.User.Id)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check email
|
|
|
|
|
if !utils.SBool(u.User.EmailVerified) {
|
|
|
|
|
a.outputFlowStep(rw, b.Subject, "email", u.User.Id)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check MFA
|
|
|
|
|
if utils.SBool(u.User.MfaActive) {
|
|
|
|
|
a.outputFlowStep(rw, b.Subject, "mfa", u.User.Id)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check MFA
|
|
|
|
|
if utils.SBool(u.User.MfaActive) {
|
|
|
|
|
a.outputFlowStep(rw, b.Subject, "mfa", u.User.Id)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Success
|
|
|
|
|
a.outputUserTokens(rw, u.User)
|
|
|
|
|
case "mfa":
|
|
|
|
|
// Check MFA code matches pattern
|
|
|
|
|
if !mfaCodePattern.MatchString(in.Mfa) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid Email Code")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Success
|
|
|
|
|
a.outputUserTokens(rw, u.User)
|
|
|
|
|
case "mfa":
|
|
|
|
|
// Check MFA code matches pattern
|
|
|
|
|
if !mfaCodePattern.MatchString(in.Mfa) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid Email Code")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user is undefined
|
|
|
|
|
if u.User == nil {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid login flow state")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check if user is undefined
|
|
|
|
|
if u.User == nil {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid login flow state")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load TOTP to check MFA code
|
|
|
|
|
totp, err := a.LoadMfaTotp(u.User)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to load multi-factor auth handler") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
err = totp.Validate(in.Mfa)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid MFA code") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Load TOTP to check MFA code
|
|
|
|
|
totp, err := a.LoadMfaTotp(u.User)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to load multi-factor auth handler") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
err = totp.Validate(in.Mfa)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid MFA code") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Success
|
|
|
|
|
a.outputUserTokens(rw, u.User)
|
|
|
|
|
case "email":
|
|
|
|
|
// Check email code matches pattern
|
|
|
|
|
if !emailCodePattern.MatchString(in.Code) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid Email Code")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Success
|
|
|
|
|
a.outputUserTokens(rw, u.User)
|
|
|
|
|
case "email":
|
|
|
|
|
// Check email code matches pattern
|
|
|
|
|
if !emailCodePattern.MatchString(in.Code) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid Email Code")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user is undefined
|
|
|
|
|
if u.User == nil {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid login flow state")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check if user is undefined
|
|
|
|
|
if u.User == nil {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid login flow state")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var zzz user.User
|
|
|
|
|
get, err := a.db.Where("id = ?", u.User.Id).Get(&zzz)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !get {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusUnauthorized, "Token Not Valid")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var zzz user.User
|
|
|
|
|
get, err := a.db.Where("id = ?", u.User.Id).Get(&zzz)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !get {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusUnauthorized, "Token Not Valid")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check email code
|
|
|
|
|
if subtle.ConstantTimeCompare([]byte(zzz.EmailToken), []byte(in.Code)) == 0 {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid email code")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check email code
|
|
|
|
|
if subtle.ConstantTimeCompare([]byte(zzz.EmailToken), []byte(in.Code)) == 0 {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid email code")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = a.db.Where("id = ?", zzz.Id).Update(&user.User{EmailVerified: utils.PBool(true)})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusBadRequest, "Failed to validate email, maybe it is already validated") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, err = a.db.Where("id = ?", zzz.Id).Update(&user.User{EmailVerified: utils.PBool(true)})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusBadRequest, "Failed to validate email, maybe it is already validated") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check MFA active
|
|
|
|
|
if utils.SBool(u.User.MfaActive) {
|
|
|
|
|
a.outputFlowStep(rw, b.Subject, "mfa", u.User.Id)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check MFA active
|
|
|
|
|
if utils.SBool(u.User.MfaActive) {
|
|
|
|
|
a.outputFlowStep(rw, b.Subject, "mfa", u.User.Id)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Success
|
|
|
|
|
a.outputUserTokens(rw, u.User)
|
|
|
|
|
default:
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid login flow state")
|
|
|
|
|
}
|
|
|
|
|
// Success
|
|
|
|
|
a.outputUserTokens(rw, u.User)
|
|
|
|
|
default:
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid login flow state")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handlePostRefresh(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Refresh token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Refresh token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify mjwt
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.RefreshTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Verify mjwt
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.RefreshTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var u user.User
|
|
|
|
|
get, err := a.db.Where("id = ?", b.Claims.UserId).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !get {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusUnauthorized, "Token Not Valid")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var u user.User
|
|
|
|
|
get, err := a.db.Where("id = ?", b.Claims.UserId).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !get {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusUnauthorized, "Token Not Valid")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.outputUserTokens(rw, &u)
|
|
|
|
|
a.outputUserTokens(rw, &u)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handlePostPasswordReset(rw http.ResponseWriter, _ *http.Request) {
|
|
|
|
|
rw.WriteHeader(http.StatusNotImplemented)
|
|
|
|
|
rw.WriteHeader(http.StatusNotImplemented)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handlePostPasswordEmail(rw http.ResponseWriter, _ *http.Request) {
|
|
|
|
|
rw.WriteHeader(http.StatusNotImplemented)
|
|
|
|
|
rw.WriteHeader(http.StatusNotImplemented)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handleMfaCreate(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get input
|
|
|
|
|
var in MfaFlowInput
|
|
|
|
|
err := json.NewDecoder(req.Body).Decode(&in)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Get input
|
|
|
|
|
var in MfaFlowInput
|
|
|
|
|
err := json.NewDecoder(req.Body).Decode(&in)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify as mfa flow token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[flow.MfaFlowClaims](a.signer, token)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Verify as mfa flow token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[flow.MfaFlowClaims](a.signer, token)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check email code matches pattern
|
|
|
|
|
if in.Digits < 6 || in.Digits > 8 {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid Number of Digits")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Check email code matches pattern
|
|
|
|
|
if in.Digits < 6 || in.Digits > 8 {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid Number of Digits")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ? and mfa_active = ?", b.Claims.UserId, false).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User or MFA is already active")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ? and mfa_active = ?", b.Claims.UserId, false).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User or MFA is already active")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totp, err := twofactor.NewTOTP(u.Email, a.signer.Issuer(), crypto.SHA512, in.Digits)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to generate MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
totpBytes, err := totp.ToBytes()
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to save MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
totp, err := twofactor.NewTOTP(u.Email, a.signer.Issuer(), crypto.SHA512, in.Digits)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to generate MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
totpBytes, err := totp.ToBytes()
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to save MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = a.db.Where("id = ?", b.Claims.UserId).Update(&user.User{MfaBytes: totpBytes})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to update MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, err = a.db.Where("id = ?", b.Claims.UserId).Update(&user.User{MfaBytes: totpBytes})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to update MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qr, err := totp.QR()
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to generate QR code") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
_, _ = base64.NewEncoder(base64.StdEncoding, buf).Write(qr)
|
|
|
|
|
qr, err := totp.QR()
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to generate QR code") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
_, _ = base64.NewEncoder(base64.StdEncoding, buf).Write(qr)
|
|
|
|
|
|
|
|
|
|
flowToken, err := flow.CreateMfaFlowToken(a.signer, b.Subject, uuid.NewString(), u.Id, time.Minute*10)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
|
rw.WriteHeader(http.StatusOK)
|
|
|
|
|
_ = json.NewEncoder(rw).Encode(MfaFlowResp{Qr: "data:image/png;base64," + buf.String(), Token: flowToken})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
flowToken, err := flow.CreateMfaFlowToken(a.signer, b.Subject, uuid.NewString(), u.Id, time.Minute*10)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
|
rw.WriteHeader(http.StatusOK)
|
|
|
|
|
_ = json.NewEncoder(rw).Encode(MfaFlowResp{Qr: "data:image/png;base64," + buf.String(), Token: flowToken})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ? and mfa_active = ?", b.Claims.LoginId, false).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User or MFA is already active")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ? and mfa_active = ?", b.Claims.LoginId, false).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User or MFA is already active")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totp, err := a.LoadMfaTotp(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to load MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
totp, err := a.LoadMfaTotp(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to load MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = totp.Validate(in.Mfa)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to validate MFA") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
err = totp.Validate(in.Mfa)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to validate MFA") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = a.db.Where("id = ?", b.Claims.LoginId).Update(&user.User{MfaActive: utils.PBool(true)})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to update MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, err = a.db.Where("id = ?", b.Claims.LoginId).Update(&user.User{MfaActive: utils.PBool(true)})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to update MFA validator") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rw.WriteHeader(http.StatusCreated)
|
|
|
|
|
rw.WriteHeader(http.StatusCreated)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handleMfaDisable(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ? and mfa_active = ?", b.Claims.UserId, false).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Invalid User or MFA is already active")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ? and mfa_active = ?", b.Claims.UserId, false).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Invalid User or MFA is already active")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = a.db.Where("id = ? and mfa_active = ?", b.Claims.UserId, true).Update(&user.User{MfaActive: utils.PBool(false)})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to disable MFA, maybe it is already disabled") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, err = a.db.Where("id = ? and mfa_active = ?", b.Claims.UserId, true).Update(&user.User{MfaActive: utils.PBool(false)})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Failed to disable MFA, maybe it is already disabled") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rw.WriteHeader(http.StatusNoContent)
|
|
|
|
|
rw.WriteHeader(http.StatusNoContent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handleGetUserInfo(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
token := utils.GetBearerToken(req)
|
|
|
|
|
if token == "" {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid Token") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Verify as access token
|
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid Token") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
z := user.CheckSpecificPerms(b.Claims.Perms, []string{"super:user", "user:info", "user:email", "user:profile"})
|
|
|
|
|
if z[0] {
|
|
|
|
|
z[1] = true
|
|
|
|
|
z[2] = true
|
|
|
|
|
z[3] = true
|
|
|
|
|
}
|
|
|
|
|
y := make(map[string]any)
|
|
|
|
|
z := user.CheckSpecificPerms(b.Claims.Perms, []string{"super:user", "user:info", "user:email", "user:profile"})
|
|
|
|
|
if z[0] {
|
|
|
|
|
z[1] = true
|
|
|
|
|
z[2] = true
|
|
|
|
|
z[3] = true
|
|
|
|
|
}
|
|
|
|
|
y := make(map[string]any)
|
|
|
|
|
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ?", b.Claims.UserId).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if ok {
|
|
|
|
|
if z[1] {
|
|
|
|
|
y["id"] = u.Id
|
|
|
|
|
y["visibility"] = user.ParseVisibility(u.Visibility)
|
|
|
|
|
y["sub"] = u.Username
|
|
|
|
|
}
|
|
|
|
|
if z[2] {
|
|
|
|
|
y["email_verified"] = utils.SBool(u.EmailVerified)
|
|
|
|
|
y["email"] = u.Email
|
|
|
|
|
}
|
|
|
|
|
if z[3] {
|
|
|
|
|
y["gender"] = utils.SString(u.Gender)
|
|
|
|
|
y["pronouns"] = user.ParsePronouns(u.Pronouns)
|
|
|
|
|
y["birthday"] = u.Birthday.String()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid User")
|
|
|
|
|
}
|
|
|
|
|
var u user.User
|
|
|
|
|
ok, err := a.db.Where("id = ?", b.Claims.UserId).Get(&u)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid User") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if ok {
|
|
|
|
|
if z[1] {
|
|
|
|
|
y["id"] = u.Id
|
|
|
|
|
y["visibility"] = user.ParseVisibility(u.Visibility)
|
|
|
|
|
y["sub"] = u.Username
|
|
|
|
|
}
|
|
|
|
|
if z[2] {
|
|
|
|
|
y["email_verified"] = utils.SBool(u.EmailVerified)
|
|
|
|
|
y["email"] = u.Email
|
|
|
|
|
}
|
|
|
|
|
if z[3] {
|
|
|
|
|
y["gender"] = utils.SString(u.Gender)
|
|
|
|
|
y["pronouns"] = user.ParsePronouns(u.Pronouns)
|
|
|
|
|
y["birthday"] = u.Birthday.String()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Invalid User")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) handlePostRegister(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
|
// Get input
|
|
|
|
|
var in RegisterFlowInput
|
|
|
|
|
err := json.NewDecoder(req.Body).Decode(&in)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !emailPattern.MatchString(in.Email) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Email")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !usernamePattern.MatchString(in.Username) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Username")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !ValidatePassword(in.Password) {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Password")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if in.Password != in.RepeatPassword {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Repeat Password")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count, err := a.db.Where("username = ?", in.Username).Count(&user.User{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Username")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if count != 0 {
|
|
|
|
|
api.GenericErrorMsg[AccountServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Username")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n := time.Now()
|
|
|
|
|
|
|
|
|
|
hashedPw, err := HashPassword(in.Password)
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Password Hashing Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = a.db.Insert(&user.User{
|
|
|
|
|
Id: 0,
|
|
|
|
|
Email: in.Email,
|
|
|
|
|
Username: in.Username,
|
|
|
|
|
Password: hashedPw,
|
|
|
|
|
Visibility: user.ProfileVisibilityPrivate.AsByte(),
|
|
|
|
|
Gender: nil,
|
|
|
|
|
Pronouns: user.ProfilePronounsUnknown.AsByte(),
|
|
|
|
|
Birthday: time.UnixMilli(-1),
|
|
|
|
|
DisplayName: in.DisplayName,
|
|
|
|
|
Created: n,
|
|
|
|
|
LastOnline: n,
|
|
|
|
|
IconId: 0,
|
|
|
|
|
MfaActive: nil,
|
|
|
|
|
MfaBytes: nil,
|
|
|
|
|
MfaObj: nil,
|
|
|
|
|
EmailVerified: utils.PBool(false),
|
|
|
|
|
EmailToken: "",
|
|
|
|
|
EnableRefresh: utils.PBool(false),
|
|
|
|
|
Enabled: utils.PBool(true),
|
|
|
|
|
})
|
|
|
|
|
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Database Error") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
rw.WriteHeader(http.StatusCreated)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AccountServer) sendEmailCheck(name, email, code string) error {
|
|
|
|
|
smtpAuth := sasl.NewPlainClient("", a.smtp.Username, a.smtp.Password)
|
|
|
|
|
|
|
|
|
|
parse, err := template.New("email").Parse(emailTemplate)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
_, _ = fmt.Fprintf(buf, "To: %s\r\n", email)
|
|
|
|
|
_, _ = fmt.Fprintf(buf, "Subject: %s\r\n", "Register Account Email")
|
|
|
|
|
buf.WriteString("MIME-version: 1.0;\r\n")
|
|
|
|
|
buf.WriteString("Content-Type: text/html; charset=\"UTF-8\";\r\n\r\n")
|
|
|
|
|
err = parse.Execute(buf, EmailTemplateStruct{
|
|
|
|
|
Title: "Register Account Email",
|
|
|
|
|
Name: name,
|
|
|
|
|
Code: code,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return smtp.SendMail(a.smtp.Server, smtpAuth, a.smtp.Username, []string{email}, buf)
|
|
|
|
|
}
|
|
|
|
|