Redesign token authentication

This commit is contained in:
Melon 2024-05-31 14:57:54 +01:00
parent 807c5c540c
commit 3555742316
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
11 changed files with 152 additions and 63 deletions

View File

@ -1,8 +1,7 @@
package server package server
import ( import (
"github.com/1f349/mjwt" "errors"
"github.com/1f349/mjwt/auth"
"github.com/1f349/tulip/database" "github.com/1f349/tulip/database"
"github.com/1f349/tulip/database/types" "github.com/1f349/tulip/database/types"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
@ -14,7 +13,7 @@ import (
type UserHandler func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) type UserHandler func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth)
type UserAuth struct { type UserAuth struct {
ID string Subject string
NeedOtp bool NeedOtp bool
} }
@ -26,14 +25,16 @@ func (u UserAuth) NextFlowUrl(origin *url.URL) *url.URL {
} }
func (u UserAuth) IsGuest() bool { func (u UserAuth) IsGuest() bool {
return u.ID == "" return u.Subject == ""
} }
var ErrAuthHttpError = errors.New("auth http error")
func (h *HttpServer) RequireAdminAuthentication(next UserHandler) httprouter.Handle { func (h *HttpServer) RequireAdminAuthentication(next UserHandler) httprouter.Handle {
return h.RequireAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) { return h.RequireAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
var role types.UserRole var role types.UserRole
if h.DbTx(rw, func(tx *database.Queries) (err error) { if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(req.Context(), auth.ID) role, err = tx.GetUserRole(req.Context(), auth.Subject)
return return
}) { }) {
return return
@ -59,27 +60,37 @@ func (h *HttpServer) RequireAuthentication(next UserHandler) httprouter.Handle {
func (h *HttpServer) OptionalAuthentication(flowPart bool, next UserHandler) httprouter.Handle { func (h *HttpServer) OptionalAuthentication(flowPart bool, next UserHandler) httprouter.Handle {
return func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { return func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
authData, err := h.internalAuthenticationHandler(req) authData, err := h.internalAuthenticationHandler(rw, req)
if err == nil { if err != nil {
if n := authData.NextFlowUrl(req.URL); n != nil && !flowPart { if !errors.Is(err, ErrAuthHttpError) {
http.Redirect(rw, req, n.String(), http.StatusFound) http.Error(rw, err.Error(), http.StatusInternalServerError)
return
} }
return
}
if n := authData.NextFlowUrl(req.URL); n != nil && !flowPart {
http.Redirect(rw, req, n.String(), http.StatusFound)
return
} }
next(rw, req, params, authData) next(rw, req, params, authData)
} }
} }
func (h *HttpServer) internalAuthenticationHandler(req *http.Request) (UserAuth, error) { func (h *HttpServer) internalAuthenticationHandler(rw http.ResponseWriter, req *http.Request) (UserAuth, error) {
if loginCookie, err := req.Cookie("tulip-login-data"); err == nil { http.SetCookie(rw, &http.Cookie{
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](h.signingKey, loginCookie.Value) Name: "tulip-login-data",
if err != nil { Path: "/",
return UserAuth{}, err MaxAge: -1,
} Secure: true,
return UserAuth{ID: b.Subject, NeedOtp: b.Claims.Perms.Has("needs-otp")}, nil SameSite: http.SameSiteLaxMode,
})
var u UserAuth
err := h.readLoginAccessCookie(rw, req, &u)
if err != nil {
// not logged in
return UserAuth{}, nil
} }
// not logged in return u, nil
return UserAuth{}, nil
} }
func PrepareRedirectUrl(targetPath string, origin *url.URL) *url.URL { func PrepareRedirectUrl(targetPath string, origin *url.URL) *url.URL {

View File

@ -21,7 +21,7 @@ func TestUserAuth_NextFlowUrl(t *testing.T) {
func TestUserAuth_IsGuest(t *testing.T) { func TestUserAuth_IsGuest(t *testing.T) {
var u UserAuth var u UserAuth
assert.True(t, u.IsGuest()) assert.True(t, u.IsGuest())
u.ID = uuid.NewString() u.Subject = uuid.NewString()
assert.False(t, u.IsGuest()) assert.False(t, u.IsGuest())
} }
@ -47,10 +47,10 @@ func TestOptionalAuthentication(t *testing.T) {
h := &HttpServer{} h := &HttpServer{}
req, err := http.NewRequest(http.MethodGet, "https://example.com/hello", nil) req, err := http.NewRequest(http.MethodGet, "https://example.com/hello", nil)
assert.NoError(t, err) assert.NoError(t, err)
auth, err := h.internalAuthenticationHandler(req) auth, err := h.internalAuthenticationHandler(nil, req)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, auth.IsGuest()) assert.True(t, auth.IsGuest())
auth.ID = "567" auth.Subject = "567"
} }
func TestPrepareRedirectUrl(t *testing.T) { func TestPrepareRedirectUrl(t *testing.T) {

View File

@ -1,22 +1,33 @@
package server package server
import ( import (
"errors"
"github.com/1f349/tulip/database" "github.com/1f349/tulip/database"
"github.com/1f349/tulip/logger" "github.com/1f349/tulip/logger"
"net/http" "net/http"
) )
var ErrDatabaseActionFailed = errors.New("database action failed")
// DbTx wraps a database transaction with http error messages and a simple action // DbTx wraps a database transaction with http error messages and a simple action
// function. If the action function returns an error the transaction will be // function. If the action function returns an error the transaction will be
// rolled back. If there is no error then the transaction is committed. // rolled back. If there is no error then the transaction is committed.
func (h *HttpServer) DbTx(rw http.ResponseWriter, action func(db *database.Queries) error) bool { func (h *HttpServer) DbTx(rw http.ResponseWriter, action func(tx *database.Queries) error) bool {
err := action(h.db) logger.Logger.Helper()
if err != nil { if h.DbTxError(action) != nil {
http.Error(rw, "Database error", http.StatusInternalServerError) http.Error(rw, "Database error", http.StatusInternalServerError)
logger.Logger.Helper()
logger.Logger.Warn("Database action error", "err", err)
return true return true
} }
return false return false
} }
func (h *HttpServer) DbTxError(action func(tx *database.Queries) error) error {
logger.Logger.Helper()
err := action(h.db)
if err != nil {
logger.Logger.Warn("Database action error", "err", err)
return ErrDatabaseActionFailed
}
return nil
}

View File

@ -16,7 +16,7 @@ func (h *HttpServer) EditGet(rw http.ResponseWriter, req *http.Request, _ httpro
if h.DbTx(rw, func(tx *database.Queries) error { if h.DbTx(rw, func(tx *database.Queries) error {
var err error var err error
user, err = tx.GetUser(req.Context(), auth.ID) user, err = tx.GetUser(req.Context(), auth.Subject)
if err != nil { if err != nil {
return fmt.Errorf("failed to read user data: %w", err) return fmt.Errorf("failed to read user data: %w", err)
} }
@ -73,7 +73,7 @@ func (h *HttpServer) EditPost(rw http.ResponseWriter, req *http.Request, _ httpr
Zoneinfo: patch.ZoneInfo, Zoneinfo: patch.ZoneInfo,
Locale: patch.Locale, Locale: patch.Locale,
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
Subject: auth.ID, Subject: auth.Subject,
} }
if h.DbTx(rw, func(tx *database.Queries) error { if h.DbTx(rw, func(tx *database.Queries) error {
if err := tx.ModifyUser(req.Context(), m); err != nil { if err := tx.ModifyUser(req.Context(), m); err != nil {

View File

@ -34,15 +34,15 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
var userRole types.UserRole var userRole types.UserRole
var hasTwoFactor bool var hasTwoFactor bool
if h.DbTx(rw, func(tx *database.Queries) (err error) { if h.DbTx(rw, func(tx *database.Queries) (err error) {
userWithName, err = tx.GetUserDisplayName(req.Context(), auth.ID) userWithName, err = tx.GetUserDisplayName(req.Context(), auth.Subject)
if err != nil { if err != nil {
return fmt.Errorf("failed to get user display name: %w", err) return fmt.Errorf("failed to get user display name: %w", err)
} }
hasTwoFactor, err = tx.HasOtp(req.Context(), auth.ID) hasTwoFactor, err = tx.HasOtp(req.Context(), auth.Subject)
if err != nil { if err != nil {
return fmt.Errorf("failed to get user two factor state: %w", err) return fmt.Errorf("failed to get user two factor state: %w", err)
} }
userRole, err = tx.GetUserRole(req.Context(), auth.ID) userRole, err = tx.GetUserRole(req.Context(), auth.Subject)
if err != nil { if err != nil {
return fmt.Errorf("failed to get user role: %w", err) return fmt.Errorf("failed to get user role: %w", err)
} }
@ -53,7 +53,7 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
pages.RenderPageTemplate(rw, "index", map[string]any{ pages.RenderPageTemplate(rw, "index", map[string]any{
"ServiceName": h.conf.ServiceName, "ServiceName": h.conf.ServiceName,
"Auth": auth, "Auth": auth,
"User": database.User{Subject: auth.ID, Name: userWithName, Role: userRole}, "User": database.User{Subject: auth.Subject, Name: userWithName, Role: userRole},
"Nonce": lNonce, "Nonce": lNonce,
"OtpEnabled": hasTwoFactor, "OtpEnabled": hasTwoFactor,
"IsAdmin": userRole == types.RoleAdmin, "IsAdmin": userRole == types.RoleAdmin,

View File

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"github.com/1f349/mjwt"
"github.com/1f349/mjwt/auth" "github.com/1f349/mjwt/auth"
"github.com/1f349/mjwt/claims" "github.com/1f349/mjwt/claims"
"github.com/1f349/tulip/database" "github.com/1f349/tulip/database"
@ -35,8 +36,8 @@ func getUserLoginName(req *http.Request) string {
return originUrl.Query().Get("login_name") return originUrl.Query().Get("login_name")
} }
func (h *HttpServer) LoginGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { func (h *HttpServer) LoginGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, userAuth UserAuth) {
if !auth.IsGuest() { if !userAuth.IsGuest() {
h.SafeRedirect(rw, req) h.SafeRedirect(rw, req)
return return
} }
@ -53,7 +54,7 @@ func (h *HttpServer) LoginGet(rw http.ResponseWriter, req *http.Request, _ httpr
}) })
} }
func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, userAuth UserAuth) {
un := req.FormValue("username") un := req.FormValue("username")
pw := req.FormValue("password") pw := req.FormValue("password")
@ -121,12 +122,13 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
} }
// only continues if the above tx succeeds // only continues if the above tx succeeds
auth = UserAuth{ userAuth = UserAuth{
ID: userInfo.Subject, Subject: userInfo.Subject,
NeedOtp: hasOtp, NeedOtp: hasOtp,
} }
if h.setLoginDataCookie(rw, auth) { if h.setLoginDataCookie(rw, userAuth) {
http.Error(rw, "Failed to save login cookie", http.StatusInternalServerError)
return return
} }
@ -144,29 +146,87 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
h.SafeRedirect(rw, req) h.SafeRedirect(rw, req)
} }
const oneYear = 365 * 24 * time.Hour const twelveHours = 12 * time.Hour
const oneMonth = 30 * 24 * time.Hour
func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, authData UserAuth) bool { func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, authData UserAuth) bool {
ps := claims.NewPermStorage() ps := claims.NewPermStorage()
if authData.NeedOtp { if authData.NeedOtp {
ps.Set("needs-otp") ps.Set("needs-otp")
} }
gen, err := h.signingKey.GenerateJwt(authData.ID, uuid.NewString(), jwt.ClaimStrings{h.conf.BaseUrl}, oneYear, auth.AccessTokenClaims{Perms: ps}) accId := uuid.NewString()
gen, err := h.signingKey.GenerateJwt(authData.Subject, accId, jwt.ClaimStrings{h.conf.BaseUrl}, twelveHours, auth.AccessTokenClaims{Perms: ps})
if err != nil {
http.Error(rw, "Failed to generate cookie token", http.StatusInternalServerError)
return true
}
ref, err := h.signingKey.GenerateJwt(authData.Subject, uuid.NewString(), jwt.ClaimStrings{h.conf.BaseUrl}, oneMonth, auth.RefreshTokenClaims{AccessTokenId: accId})
if err != nil { if err != nil {
http.Error(rw, "Failed to generate cookie token", http.StatusInternalServerError) http.Error(rw, "Failed to generate cookie token", http.StatusInternalServerError)
return true return true
} }
http.SetCookie(rw, &http.Cookie{ http.SetCookie(rw, &http.Cookie{
Name: "tulip-login-data", Name: "tulip-login-access",
Value: gen, Value: gen,
Path: "/", Path: "/",
Expires: time.Now().AddDate(1, 0, 0),
Secure: true, Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
http.SetCookie(rw, &http.Cookie{
Name: "tulip-login-refresh",
Value: ref,
Path: "/",
Expires: time.Now().AddDate(0, 0, 32),
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode, SameSite: http.SameSiteLaxMode,
}) })
return false return false
} }
func readJwtCookie[T mjwt.Claims](req *http.Request, cookieName string, signingKey mjwt.Verifier) (mjwt.BaseTypeClaims[T], error) {
loginCookie, err := req.Cookie(cookieName)
if err != nil {
return mjwt.BaseTypeClaims[T]{}, err
}
_, b, err := mjwt.ExtractClaims[T](signingKey, loginCookie.Value)
if err != nil {
return mjwt.BaseTypeClaims[T]{}, err
}
return b, nil
}
func (h *HttpServer) readLoginAccessCookie(rw http.ResponseWriter, req *http.Request, u *UserAuth) error {
loginData, err := readJwtCookie[auth.AccessTokenClaims](req, "tulip-login-access", h.signingKey)
if err != nil {
return h.readLoginRefreshCookie(rw, req, u)
}
*u = UserAuth{
Subject: loginData.Subject,
NeedOtp: loginData.Claims.Perms.Has("needs-otp"),
}
return nil
}
func (h *HttpServer) readLoginRefreshCookie(rw http.ResponseWriter, req *http.Request, userAuth *UserAuth) error {
refreshData, err := readJwtCookie[auth.RefreshTokenClaims](req, "tulip-login-refresh", h.signingKey)
if err != nil {
return err
}
*userAuth = UserAuth{
Subject: refreshData.Subject,
NeedOtp: false,
}
if h.setLoginDataCookie(rw, *userAuth) {
http.Error(rw, "Failed to save login cookie", http.StatusInternalServerError)
return fmt.Errorf("failed to save login cookie: %w", ErrAuthHttpError)
}
return nil
}
func (h *HttpServer) LoginResetPasswordPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) { func (h *HttpServer) LoginResetPasswordPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
email := req.PostFormValue("email") email := req.PostFormValue("email")
address, err := mail.ParseAddress(email) address, err := mail.ParseAddress(email)

View File

@ -27,12 +27,12 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
var role types.UserRole var role types.UserRole
var appList []database.GetAppListRow var appList []database.GetAppListRow
if h.DbTx(rw, func(tx *database.Queries) (err error) { if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(req.Context(), auth.ID) role, err = tx.GetUserRole(req.Context(), auth.Subject)
if err != nil { if err != nil {
return return
} }
appList, err = tx.GetAppList(req.Context(), database.GetAppListParams{ appList, err = tx.GetAppList(req.Context(), database.GetAppListParams{
Owner: auth.ID, Owner: auth.Subject,
Column2: role == types.RoleAdmin, Column2: role == types.RoleAdmin,
Offset: int64(offset), Offset: int64(offset),
}) })
@ -84,7 +84,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
if sso { if sso {
var role types.UserRole var role types.UserRole
if h.DbTx(rw, func(tx *database.Queries) (err error) { if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(req.Context(), auth.ID) role, err = tx.GetUserRole(req.Context(), auth.Subject)
return return
}) { }) {
return return
@ -107,7 +107,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
Name: name, Name: name,
Secret: secret, Secret: secret,
Domain: domain, Domain: domain,
Owner: auth.ID, Owner: auth.Subject,
Public: public, Public: public,
Sso: sso, Sso: sso,
Active: active, Active: active,
@ -124,7 +124,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
Sso: sso, Sso: sso,
Active: active, Active: active,
Subject: req.FormValue("subject"), Subject: req.FormValue("subject"),
Owner: auth.ID, Owner: auth.Subject,
}) })
}) { }) {
return return
@ -145,7 +145,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
err = tx.ResetClientAppSecret(req.Context(), database.ResetClientAppSecretParams{ err = tx.ResetClientAppSecret(req.Context(), database.ResetClientAppSecretParams{
Secret: secret, Secret: secret,
Subject: sub, Subject: sub,
Owner: auth.ID, Owner: auth.Subject,
}) })
return err return err
}) { }) {

View File

@ -31,7 +31,7 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _
var role types.UserRole var role types.UserRole
var userList []database.GetUserListRow var userList []database.GetUserListRow
if h.DbTx(rw, func(tx *database.Queries) (err error) { if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(req.Context(), auth.ID) role, err = tx.GetUserRole(req.Context(), auth.Subject)
if err != nil { if err != nil {
return return
} }
@ -51,7 +51,7 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _
"Users": userList, "Users": userList,
"Offset": offset, "Offset": offset,
"EmailShow": req.URL.Query().Has("show-email"), "EmailShow": req.URL.Query().Has("show-email"),
"CurrentAdmin": auth.ID, "CurrentAdmin": auth.Subject,
"Namespace": h.conf.Namespace, "Namespace": h.conf.Namespace,
} }
if q.Has("edit") { if q.Has("edit") {
@ -80,7 +80,7 @@ func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request,
var role types.UserRole var role types.UserRole
if h.DbTx(rw, func(tx *database.Queries) (err error) { if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(req.Context(), auth.ID) role, err = tx.GetUserRole(req.Context(), auth.Subject)
return return
}) { }) {
return return

View File

@ -80,11 +80,11 @@ func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request
var user string var user string
var hasOtp bool var hasOtp bool
if h.DbTx(rw, func(tx *database.Queries) (err error) { if h.DbTx(rw, func(tx *database.Queries) (err error) {
user, err = tx.GetUserDisplayName(req.Context(), auth.ID) user, err = tx.GetUserDisplayName(req.Context(), auth.Subject)
if err != nil { if err != nil {
return return
} }
hasOtp, err = tx.HasOtp(req.Context(), auth.ID) hasOtp, err = tx.HasOtp(req.Context(), auth.Subject)
return return
}) { }) {
return return
@ -117,7 +117,7 @@ func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request
if !isSSO { if !isSSO {
otpInput := req.FormValue("code") otpInput := req.FormValue("code")
if h.fetchAndValidateOtp(rw, auth.ID, otpInput) { if h.fetchAndValidateOtp(rw, auth.Subject, otpInput) {
return return
} }
} }
@ -147,7 +147,7 @@ func (h *HttpServer) oauthUserAuthorization(rw http.ResponseWriter, req *http.Re
return "", err return "", err
} }
auth, err := h.internalAuthenticationHandler(req) auth, err := h.internalAuthenticationHandler(rw, req)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -169,5 +169,5 @@ func (h *HttpServer) oauthUserAuthorization(rw http.ResponseWriter, req *http.Re
http.Redirect(rw, req, redirectUrl.String(), http.StatusFound) http.Redirect(rw, req, redirectUrl.String(), http.StatusFound)
return "", nil return "", nil
} }
return auth.ID, nil return auth.Subject, nil
} }

View File

@ -34,7 +34,7 @@ func (h *HttpServer) LoginOtpPost(rw http.ResponseWriter, req *http.Request, _ h
} }
otpInput := req.FormValue("code") otpInput := req.FormValue("code")
if h.fetchAndValidateOtp(rw, auth.ID, otpInput) { if h.fetchAndValidateOtp(rw, auth.Subject, otpInput) {
return return
} }
@ -86,13 +86,13 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
} }
otpInput := req.Form.Get("code") otpInput := req.Form.Get("code")
if h.fetchAndValidateOtp(rw, auth.ID, otpInput) { if h.fetchAndValidateOtp(rw, auth.Subject, otpInput) {
return return
} }
if h.DbTx(rw, func(tx *database.Queries) error { if h.DbTx(rw, func(tx *database.Queries) error {
return tx.SetOtp(req.Context(), database.SetOtpParams{ return tx.SetOtp(req.Context(), database.SetOtpParams{
Subject: auth.ID, Subject: auth.Subject,
Secret: "", Secret: "",
Digits: 0, Digits: 0,
}) })
@ -128,7 +128,7 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
var email string var email string
if h.DbTx(rw, func(tx *database.Queries) error { if h.DbTx(rw, func(tx *database.Queries) error {
var err error var err error
email, err = tx.GetUserEmail(req.Context(), auth.ID) email, err = tx.GetUserEmail(req.Context(), auth.Subject)
return err return err
}) { }) {
return return
@ -177,7 +177,7 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
if h.DbTx(rw, func(tx *database.Queries) error { if h.DbTx(rw, func(tx *database.Queries) error {
return tx.SetOtp(req.Context(), database.SetOtpParams{ return tx.SetOtp(req.Context(), database.SetOtpParams{
Subject: auth.ID, Subject: auth.Subject,
Secret: secret, Secret: secret,
Digits: int64(digits), Digits: int64(digits),
}) })

View File

@ -124,7 +124,14 @@ func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *htt
} }
if subtle.ConstantTimeCompare([]byte(cookie.Value), []byte(req.PostFormValue("nonce"))) == 1 { if subtle.ConstantTimeCompare([]byte(cookie.Value), []byte(req.PostFormValue("nonce"))) == 1 {
http.SetCookie(rw, &http.Cookie{ http.SetCookie(rw, &http.Cookie{
Name: "tulip-login-data", Name: "tulip-login-access",
Path: "/",
MaxAge: -1,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
http.SetCookie(rw, &http.Cookie{
Name: "tulip-login-refresh",
Path: "/", Path: "/",
MaxAge: -1, MaxAge: -1,
Secure: true, Secure: true,