Finish up initdb implementation

This commit is contained in:
Melon 2024-03-12 21:04:25 +00:00
parent 1a7c13bb51
commit 20654eb70d
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
20 changed files with 316 additions and 177 deletions

View File

@ -3,13 +3,13 @@ package main
import (
"context"
"crypto/rand"
"database/sql"
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/1f349/mjwt"
"github.com/1f349/tulip"
"github.com/1f349/tulip/database"
"github.com/1f349/tulip/database/types"
"github.com/1f349/tulip/mail/templates"
"github.com/1f349/tulip/pages"
"github.com/1f349/tulip/server"
@ -20,6 +20,7 @@ import (
"log"
"os"
"path/filepath"
"time"
)
type serveCmd struct{ configPath string }
@ -78,7 +79,7 @@ func normalLoad(startUp server.Conf, wd string) {
log.Fatal("[Tulip] Failed to open signing key file:", err)
}
db, err := database.Open(filepath.Join(wd, "tulip.db.sqlite"))
db, err := tulip.InitDB(filepath.Join(wd, "tulip.db.sqlite"))
if err != nil {
log.Fatal("[Tulip] Failed to open database:", err)
}
@ -102,42 +103,30 @@ func normalLoad(startUp server.Conf, wd string) {
exit_reload.ExitReload("Tulip", func() {}, func() {
// stop http server
_ = srv.Close()
_ = db.Close()
})
}
func genHmacKey() []byte {
a := make([]byte, 32)
n, err := rand.Reader.Read(a)
if err != nil {
log.Fatal("[Tulip] Failed to generate HMAC key")
}
if n != 32 {
log.Fatal("[Tulip] Failed to generate HMAC key")
}
return a
}
func checkDbHasUser(db *database.Queries) error {
tx, err := db.Begin()
value, err := db.HasUser(context.Background())
if err != nil {
return fmt.Errorf("failed to start transaction: %w", err)
return err
}
defer tx.Rollback()
if err := tx.HasUser(); err != nil {
if errors.Is(err, sql.ErrNoRows) {
_, err := tx.InsertUser("Admin", "admin", "admin", "admin@localhost", false, types.RoleAdmin, false)
if err != nil {
return fmt.Errorf("failed to add user: %w", err)
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
// continue normal operation now
return nil
} else {
return fmt.Errorf("failed to check if table has a user: %w", err)
if !value {
_, err := db.AddUser(context.Background(), database.AddUserParams{
Name: "Admin",
Username: "admin",
Password: "admin",
Email: "admin@localhost",
EmailVerified: false,
Role: types.RoleAdmin,
UpdatedAt: time.Now(),
Active: false,
})
if err != nil {
return fmt.Errorf("failed to add user: %w", err)
}
// continue normal operation now
}
return nil
}

View File

@ -117,6 +117,24 @@ func (q *Queries) InsertClientApp(ctx context.Context, arg InsertClientAppParams
return err
}
const resetClientAppSecret = `-- name: ResetClientAppSecret :exec
UPDATE client_store
SET secret = ?
WHERE subject = ?
AND owner = ?
`
type ResetClientAppSecretParams struct {
Secret string `json:"secret"`
Subject string `json:"subject"`
Owner string `json:"owner"`
}
func (q *Queries) ResetClientAppSecret(ctx context.Context, arg ResetClientAppSecretParams) error {
_, err := q.db.ExecContext(ctx, resetClientAppSecret, arg.Secret, arg.Subject, arg.Owner)
return err
}
const updateClientApp = `-- name: UpdateClientApp :exec
UPDATE client_store
SET name = ?,
@ -150,21 +168,3 @@ func (q *Queries) UpdateClientApp(ctx context.Context, arg UpdateClientAppParams
)
return err
}
const resetClientAppSecret = `-- name: resetClientAppSecret :exec
UPDATE client_store
SET secret = ?
WHERE subject = ?
AND owner = ?
`
type resetClientAppSecretParams struct {
Secret string `json:"secret"`
Subject string `json:"subject"`
Owner string `json:"owner"`
}
func (q *Queries) resetClientAppSecret(ctx context.Context, arg resetClientAppSecretParams) error {
_, err := q.db.ExecContext(ctx, resetClientAppSecret, arg.Secret, arg.Subject, arg.Owner)
return err
}

View File

@ -90,14 +90,14 @@ func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams)
}
const userEmailExists = `-- name: UserEmailExists :one
SELECT EXISTS(SELECT 1 FROM users WHERE email = ? AND email_verified = 1)
SELECT CAST(EXISTS(SELECT 1 FROM users WHERE email = ? AND email_verified = 1) AS BOOLEAN) AS email_exists
`
func (q *Queries) UserEmailExists(ctx context.Context, email string) (int64, error) {
func (q *Queries) UserEmailExists(ctx context.Context, email string) (bool, error) {
row := q.db.QueryRowContext(ctx, userEmailExists, email)
var column_1 int64
err := row.Scan(&column_1)
return column_1, err
var email_exists bool
err := row.Scan(&email_exists)
return email_exists, err
}
const verifyUserEmail = `-- name: VerifyUserEmail :exec

View File

@ -40,7 +40,8 @@ func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) (string, error
type CheckLoginResult struct {
Subject string `json:"subject"`
HasTwoFactor bool `json:"hasTwoFactor"`
Name string `json:"name"`
HasOtp bool `json:"hasTwoFactor"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
@ -56,8 +57,26 @@ func (q *Queries) CheckLogin(ctx context.Context, un, pw string) (CheckLoginResu
}
return CheckLoginResult{
Subject: login.Subject,
HasTwoFactor: login.HasOtp,
Name: login.Name,
HasOtp: login.HasOtp,
Email: login.Email,
EmailVerified: login.EmailVerified,
}, nil
}
func (q *Queries) ChangePassword(ctx context.Context, subject, newPw string) error {
userPassword, err := q.getUserPassword(ctx, subject)
if err != nil {
return err
}
newPwHash, err := password.HashPassword(newPw)
if err != nil {
return err
}
return q.changeUserPassword(ctx, changeUserPasswordParams{
Password: newPwHash,
UpdatedAt: time.Now(),
Subject: subject,
Password_2: userPassword,
})
}

View File

@ -25,7 +25,7 @@ SET name = ?,
WHERE subject = ?
AND owner = ?;
-- name: resetClientAppSecret :exec
-- name: ResetClientAppSecret :exec
UPDATE client_store
SET secret = ?
WHERE subject = ?

View File

@ -23,4 +23,4 @@ SET email_verified = 1
WHERE subject = ?;
-- name: UserEmailExists :one
SELECT EXISTS(SELECT 1 FROM users WHERE email = ? AND email_verified = 1);
SELECT CAST(EXISTS(SELECT 1 FROM users WHERE email = ? AND email_verified = 1) AS BOOLEAN) AS email_exists;

View File

@ -1,5 +1,5 @@
-- name: HasUser :one
SELECT cast(count(subject) AS BOOLEAN) AS hasUser
SELECT CAST(count(subject) AS BOOLEAN) AS hasUser
FROM users;
-- name: addUser :exec
@ -7,7 +7,7 @@ INSERT INTO users (subject, name, username, password, email, email_verified, rol
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
-- name: checkLogin :one
SELECT subject, password, cast(EXISTS(SELECT 1 FROM otp WHERE otp.subject = users.subject) AS BOOLEAN) as has_otp, email, email_verified
SELECT subject, name, password, CAST(EXISTS(SELECT 1 FROM otp WHERE otp.subject = users.subject) AS BOOLEAN) AS has_otp, email, email_verified
FROM users
WHERE username = ?
LIMIT 1;
@ -33,14 +33,14 @@ SELECT password
FROM users
WHERE subject = ?;
-- name: changeUserPassword :execrows
-- name: changeUserPassword :exec
UPDATE users
SET password = ?,
updated_at = ?
WHERE subject = ?
AND password = ?;
-- name: ModifyUser :execrows
-- name: ModifyUser :exec
UPDATE users
SET name = ?,
picture = ?,
@ -52,21 +52,26 @@ SET name = ?,
updated_at=?
WHERE subject = ?;
-- name: SetTwoFactor :exec
-- name: SetOtp :exec
INSERT OR
REPLACE
INTO otp (subject, secret, digits)
VALUES (?, ?, ?);
-- name: DeleteTwoFactor :exec
-- name: DeleteOtp :exec
DELETE
FROM otp
WHERE otp.subject = ?;
-- name: GetTwoFactor :one
-- name: GetOtp :one
SELECT secret, digits
FROM otp
WHERE subject = ?;
-- name: HasTwoFactor :one
SELECT cast(EXISTS(SELECT 1 FROM otp WHERE subject = ?) AS BOOLEAN);
-- name: HasOtp :one
SELECT CAST(EXISTS(SELECT 1 FROM otp WHERE subject = ?) AS BOOLEAN);
-- name: GetUserEmail :one
SELECT email
FROM users
WHERE subject = ?;

View File

@ -14,31 +14,31 @@ import (
"github.com/1f349/tulip/password"
)
const deleteTwoFactor = `-- name: DeleteTwoFactor :exec
const deleteOtp = `-- name: DeleteOtp :exec
DELETE
FROM otp
WHERE otp.subject = ?
`
func (q *Queries) DeleteTwoFactor(ctx context.Context, subject string) error {
_, err := q.db.ExecContext(ctx, deleteTwoFactor, subject)
func (q *Queries) DeleteOtp(ctx context.Context, subject string) error {
_, err := q.db.ExecContext(ctx, deleteOtp, subject)
return err
}
const getTwoFactor = `-- name: GetTwoFactor :one
const getOtp = `-- name: GetOtp :one
SELECT secret, digits
FROM otp
WHERE subject = ?
`
type GetTwoFactorRow struct {
type GetOtpRow struct {
Secret string `json:"secret"`
Digits int64 `json:"digits"`
}
func (q *Queries) GetTwoFactor(ctx context.Context, subject string) (GetTwoFactorRow, error) {
row := q.db.QueryRowContext(ctx, getTwoFactor, subject)
var i GetTwoFactorRow
func (q *Queries) GetOtp(ctx context.Context, subject string) (GetOtpRow, error) {
row := q.db.QueryRowContext(ctx, getOtp, subject)
var i GetOtpRow
err := row.Scan(&i.Secret, &i.Digits)
return i, err
}
@ -87,6 +87,19 @@ func (q *Queries) GetUserDisplayName(ctx context.Context, subject string) (strin
return name, err
}
const getUserEmail = `-- name: GetUserEmail :one
SELECT email
FROM users
WHERE subject = ?
`
func (q *Queries) GetUserEmail(ctx context.Context, subject string) (string, error) {
row := q.db.QueryRowContext(ctx, getUserEmail, subject)
var email string
err := row.Scan(&email)
return email, err
}
const getUserRole = `-- name: GetUserRole :one
SELECT role
FROM users
@ -100,19 +113,19 @@ func (q *Queries) GetUserRole(ctx context.Context, subject string) (types.UserRo
return role, err
}
const hasTwoFactor = `-- name: HasTwoFactor :one
SELECT cast(EXISTS(SELECT 1 FROM otp WHERE subject = ?) AS BOOLEAN)
const hasOtp = `-- name: HasOtp :one
SELECT CAST(EXISTS(SELECT 1 FROM otp WHERE subject = ?) AS BOOLEAN)
`
func (q *Queries) HasTwoFactor(ctx context.Context, subject string) (bool, error) {
row := q.db.QueryRowContext(ctx, hasTwoFactor, subject)
func (q *Queries) HasOtp(ctx context.Context, subject string) (bool, error) {
row := q.db.QueryRowContext(ctx, hasOtp, subject)
var column_1 bool
err := row.Scan(&column_1)
return column_1, err
}
const hasUser = `-- name: HasUser :one
SELECT cast(count(subject) AS BOOLEAN) AS hasUser
SELECT CAST(count(subject) AS BOOLEAN) AS hasUser
FROM users
`
@ -123,7 +136,7 @@ func (q *Queries) HasUser(ctx context.Context) (bool, error) {
return hasuser, err
}
const modifyUser = `-- name: ModifyUser :execrows
const modifyUser = `-- name: ModifyUser :exec
UPDATE users
SET name = ?,
picture = ?,
@ -148,8 +161,8 @@ type ModifyUserParams struct {
Subject string `json:"subject"`
}
func (q *Queries) ModifyUser(ctx context.Context, arg ModifyUserParams) (int64, error) {
result, err := q.db.ExecContext(ctx, modifyUser,
func (q *Queries) ModifyUser(ctx context.Context, arg ModifyUserParams) error {
_, err := q.db.ExecContext(ctx, modifyUser,
arg.Name,
arg.Picture,
arg.Website,
@ -160,27 +173,24 @@ func (q *Queries) ModifyUser(ctx context.Context, arg ModifyUserParams) (int64,
arg.UpdatedAt,
arg.Subject,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
return err
}
const setTwoFactor = `-- name: SetTwoFactor :exec
const setOtp = `-- name: SetOtp :exec
INSERT OR
REPLACE
INTO otp (subject, secret, digits)
VALUES (?, ?, ?)
`
type SetTwoFactorParams struct {
type SetOtpParams struct {
Subject string `json:"subject"`
Secret string `json:"secret"`
Digits int64 `json:"digits"`
}
func (q *Queries) SetTwoFactor(ctx context.Context, arg SetTwoFactorParams) error {
_, err := q.db.ExecContext(ctx, setTwoFactor, arg.Subject, arg.Secret, arg.Digits)
func (q *Queries) SetOtp(ctx context.Context, arg SetOtpParams) error {
_, err := q.db.ExecContext(ctx, setOtp, arg.Subject, arg.Secret, arg.Digits)
return err
}
@ -216,7 +226,7 @@ func (q *Queries) addUser(ctx context.Context, arg addUserParams) error {
return err
}
const changeUserPassword = `-- name: changeUserPassword :execrows
const changeUserPassword = `-- name: changeUserPassword :exec
UPDATE users
SET password = ?,
updated_at = ?
@ -231,21 +241,18 @@ type changeUserPasswordParams struct {
Password_2 password.HashString `json:"password_2"`
}
func (q *Queries) changeUserPassword(ctx context.Context, arg changeUserPasswordParams) (int64, error) {
result, err := q.db.ExecContext(ctx, changeUserPassword,
func (q *Queries) changeUserPassword(ctx context.Context, arg changeUserPasswordParams) error {
_, err := q.db.ExecContext(ctx, changeUserPassword,
arg.Password,
arg.UpdatedAt,
arg.Subject,
arg.Password_2,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
return err
}
const checkLogin = `-- name: checkLogin :one
SELECT subject, password, cast(EXISTS(SELECT 1 FROM otp WHERE otp.subject = users.subject) AS BOOLEAN) as has_otp, email, email_verified
SELECT subject, name, password, CAST(EXISTS(SELECT 1 FROM otp WHERE otp.subject = users.subject) AS BOOLEAN) AS has_otp, email, email_verified
FROM users
WHERE username = ?
LIMIT 1
@ -253,6 +260,7 @@ LIMIT 1
type checkLoginRow struct {
Subject string `json:"subject"`
Name string `json:"name"`
Password password.HashString `json:"password"`
HasOtp bool `json:"has_otp"`
Email string `json:"email"`
@ -264,6 +272,7 @@ func (q *Queries) checkLogin(ctx context.Context, username string) (checkLoginRo
var i checkLoginRow
err := row.Scan(
&i.Subject,
&i.Name,
&i.Password,
&i.HasOtp,
&i.Email,

14
go.mod
View File

@ -4,7 +4,7 @@ go 1.22
require (
github.com/1f349/cache v0.0.2
github.com/1f349/mjwt v0.2.2
github.com/1f349/mjwt v0.2.5
github.com/1f349/overlapfs v0.0.1
github.com/1f349/violet v0.0.13
github.com/MrMelon54/exit-reload v0.0.1
@ -14,14 +14,15 @@ require (
github.com/emersion/go-smtp v0.20.2
github.com/go-oauth2/oauth2/v4 v4.5.2
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-migrate/migrate/v4 v4.17.0
github.com/google/subcommands v1.2.0
github.com/google/uuid v1.6.0
github.com/julienschmidt/httprouter v1.3.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/xlzd/gotp v0.1.0
golang.org/x/crypto v0.19.0
golang.org/x/crypto v0.21.0
golang.org/x/text v0.14.0
)
@ -31,16 +32,19 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/tidwall/buntdb v1.3.0 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
golang.org/x/net v0.21.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/net v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

37
go.sum
View File

@ -1,8 +1,8 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/1f349/cache v0.0.2 h1:27QD6zPd9xYyvh9V1qqWq+EAt5+N+qvyGWKfnjMrhP8=
github.com/1f349/cache v0.0.2/go.mod h1:LibAMy13dF0KO1fQA9aEjZPBCB6Y4b5kKYEQJUqc2rQ=
github.com/1f349/mjwt v0.2.2 h1:mVw71zcf0D7dWgZXMvjXMq8oNn41V1DFyLY0Ywkq1VQ=
github.com/1f349/mjwt v0.2.2/go.mod h1:KEs6jd9JjWrQW+8feP2pGAU7pdA3aYTqjkT/YQr73PU=
github.com/1f349/mjwt v0.2.5 h1:IxjLaali22ayTzZ628lH7j0JDdYJoj6+CJ/VktCqtXQ=
github.com/1f349/mjwt v0.2.5/go.mod h1:KEs6jd9JjWrQW+8feP2pGAU7pdA3aYTqjkT/YQr73PU=
github.com/1f349/overlapfs v0.0.1 h1:LAxBolrXFAgU0yqZtXg/C/aaPq3eoQSPpBc49BHuTp0=
github.com/1f349/overlapfs v0.0.1/go.mod h1:I6aItQycr7nrzplmfNXp/QF9tTmKRSgY3fXmu/7Ky2o=
github.com/1f349/violet v0.0.13 h1:lJpTz15Ea83Uc1VAISXTjtKuzr8Pe8NM4cMGp3Aiyhk=
@ -46,6 +46,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@ -57,8 +59,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -69,6 +72,11 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
@ -77,14 +85,17 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
@ -116,8 +127,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
@ -128,8 +139,8 @@ github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA=
github.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
@ -169,11 +180,13 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -188,8 +201,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

38
initdb.go Normal file
View File

@ -0,0 +1,38 @@
package tulip
import (
"database/sql"
"embed"
"errors"
"github.com/1f349/tulip/database"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite3"
"github.com/golang-migrate/migrate/v4/source/iofs"
)
//go:embed database/migrations/*.sql
var migrations embed.FS
func InitDB(p string) (*database.Queries, error) {
migDrv, err := iofs.New(migrations, "database/migrations")
if err != nil {
return nil, err
}
dbOpen, err := sql.Open("sqlite3", p)
if err != nil {
return nil, err
}
dbDrv, err := sqlite3.WithInstance(dbOpen, &sqlite3.Config{})
if err != nil {
return nil, err
}
mig, err := migrate.NewWithInstance("iofs", migDrv, "sqlite3", dbDrv)
if err != nil {
return nil, err
}
err = mig.Up()
if err != nil && !errors.Is(err, migrate.ErrNoChange) {
return nil, err
}
return database.New(dbOpen), nil
}

View File

@ -76,7 +76,7 @@ func (h *HttpServer) EditPost(rw http.ResponseWriter, req *http.Request, _ httpr
Subject: auth.ID,
}
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 {
return fmt.Errorf("failed to modify user info: %w", err)
}
return nil

View File

@ -38,7 +38,7 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
if err != nil {
return fmt.Errorf("failed to get user display name: %w", err)
}
hasTwoFactor, err = tx.HasTwoFactor(req.Context(), auth.ID)
hasTwoFactor, err = tx.HasOtp(req.Context(), auth.ID)
if err != nil {
return fmt.Errorf("failed to get user two factor state: %w", err)
}

View File

@ -58,7 +58,7 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
pw := req.FormValue("password")
// flags returned from database call
var userInfo *database.User
var userInfo database.CheckLoginResult
var loginMismatch byte
var hasOtp bool
@ -74,8 +74,8 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
}
userInfo = loginUser
hasOtp = hasOtpRaw
if !hasVerifiedEmail {
hasOtp = loginUser.HasOtp
if !loginUser.EmailVerified {
loginMismatch = 2
}
return nil
@ -100,7 +100,7 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
}
u := uuid.NewString()
h.mailLinkCache.Set(mailLinkKey{mailLinkVerifyEmail, u}, userInfo.Sub, time.Now().Add(10*time.Minute))
h.mailLinkCache.Set(mailLinkKey{mailLinkVerifyEmail, u}, userInfo.Subject, time.Now().Add(10*time.Minute))
// try to send email
err = h.conf.Mail.SendEmailTemplate("mail-verify", "Verify Email", userInfo.Name, address, map[string]any{
@ -122,7 +122,7 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
// only continues if the above tx succeeds
auth = UserAuth{
ID: userInfo.Sub,
ID: userInfo.Subject,
NeedOtp: hasOtp,
}
@ -177,7 +177,7 @@ func (h *HttpServer) LoginResetPasswordPost(rw http.ResponseWriter, req *http.Re
var emailExists bool
if h.DbTx(rw, func(tx *database.Queries) (err error) {
emailExists, err = tx.UserEmailExists(email)
emailExists, err = tx.UserEmailExists(req.Context(), email)
return err
}) {
return
@ -190,4 +190,6 @@ func (h *HttpServer) LoginResetPasswordPost(rw http.ResponseWriter, req *http.Re
func (h *HttpServer) possiblySendPasswordResetEmail(email string, exists bool) {
// TODO(Melon): Send reset password email template
_ = email
_ = exists
}

View File

@ -2,13 +2,14 @@ package server
import (
"github.com/1f349/tulip/database"
"github.com/1f349/tulip/database/types"
"github.com/1f349/tulip/pages"
"github.com/emersion/go-message/mail"
"github.com/julienschmidt/httprouter"
"net/http"
)
func (h *HttpServer) MailVerify(rw http.ResponseWriter, _ *http.Request, params httprouter.Params) {
func (h *HttpServer) MailVerify(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
code := params.ByName("code")
k := mailLinkKey{mailLinkVerifyEmail, code}
@ -19,7 +20,7 @@ func (h *HttpServer) MailVerify(rw http.ResponseWriter, _ *http.Request, params
return
}
if h.DbTx(rw, func(tx *database.Queries) error {
return tx.VerifyUserEmail(userSub)
return tx.VerifyUserEmail(req.Context(), userSub)
}) {
return
}
@ -76,7 +77,7 @@ func (h *HttpServer) MailPasswordPost(rw http.ResponseWriter, req *http.Request,
// reset password database call
if h.DbTx(rw, func(tx *database.Queries) error {
return tx.UserResetPassword(userSub, pw)
return tx.ChangePassword(req.Context(), userSub, pw)
}) {
return
}
@ -84,7 +85,7 @@ func (h *HttpServer) MailPasswordPost(rw http.ResponseWriter, req *http.Request,
http.Error(rw, "Reset password successfully, you can login now.", http.StatusOK)
}
func (h *HttpServer) MailDelete(rw http.ResponseWriter, _ *http.Request, params httprouter.Params) {
func (h *HttpServer) MailDelete(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
code := params.ByName("code")
k := mailLinkKey{mailLinkDelete, code}
@ -93,13 +94,17 @@ func (h *HttpServer) MailDelete(rw http.ResponseWriter, _ *http.Request, params
http.Error(rw, "Invalid email delete code", http.StatusBadRequest)
return
}
var userInfo *database.User
var userInfo database.User
if h.DbTx(rw, func(tx *database.Queries) (err error) {
userInfo, err = tx.GetUser(userSub)
userInfo, err = tx.GetUser(req.Context(), userSub)
if err != nil {
return
}
return tx.UpdateUser(userSub, types.RoleToDelete, false)
return tx.UpdateUserRole(req.Context(), database.UpdateUserRoleParams{
Active: false,
Role: types.RoleToDelete,
Subject: userSub,
})
}) {
return
}

View File

@ -2,8 +2,10 @@ package server
import (
"github.com/1f349/tulip/database"
"github.com/1f349/tulip/database/types"
"github.com/1f349/tulip/pages"
"github.com/go-oauth2/oauth2/v4"
"github.com/1f349/tulip/password"
"github.com/google/uuid"
"github.com/julienschmidt/httprouter"
"net/http"
"net/url"
@ -23,13 +25,17 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
}
var role types.UserRole
var appList []database.ClientStore
var appList []database.GetAppListRow
if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(auth.ID)
role, err = tx.GetUserRole(req.Context(), auth.ID)
if err != nil {
return
}
appList, err = tx.GetAppList(auth.ID, role == types.RoleAdmin, offset)
appList, err = tx.GetAppList(req.Context(), database.GetAppListParams{
Owner: auth.ID,
Column2: role == types.RoleAdmin,
Offset: int64(offset),
})
return
}) {
return
@ -45,7 +51,7 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
}
if q.Has("edit") {
for _, i := range appList {
if i.Sub == q.Get("edit") {
if i.Subject == q.Get("edit") {
m["Edit"] = i
goto validEdit
}
@ -78,7 +84,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
if sso {
var role types.UserRole
if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(auth.ID)
role, err = tx.GetUserRole(req.Context(), auth.ID)
return
}) {
return
@ -92,35 +98,61 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
switch action {
case "create":
if h.DbTx(rw, func(tx *database.Queries) error {
return tx.InsertClientApp(name, domain, public, sso, active, auth.ID)
secret, err := password.GenerateApiSecret(70)
if err != nil {
return err
}
return tx.InsertClientApp(req.Context(), database.InsertClientAppParams{
Subject: uuid.NewString(),
Name: name,
Secret: secret,
Domain: domain,
Owner: auth.ID,
Public: public,
Sso: sso,
Active: active,
})
}) {
return
}
case "edit":
if h.DbTx(rw, func(tx *database.Queries) error {
return tx.UpdateClientApp(req.Form.Get("subject"), auth.ID, name, domain, public, sso, active)
return tx.UpdateClientApp(req.Context(), database.UpdateClientAppParams{
Name: name,
Domain: domain,
Public: public,
Sso: sso,
Active: active,
Subject: req.FormValue("subject"),
Owner: auth.ID,
})
}) {
return
}
case "secret":
var info oauth2.ClientInfo
var info database.ClientStore
var secret string
if h.DbTx(rw, func(tx *database.Queries) error {
sub := req.Form.Get("subject")
info, err = tx.GetClientInfo(sub)
info, err = tx.GetClientInfo(req.Context(), sub)
if err != nil {
return err
}
secret, err = tx.ResetClientAppSecret(sub, auth.ID)
secret, err := password.GenerateApiSecret(70)
if err != nil {
return err
}
err = tx.ResetClientAppSecret(req.Context(), database.ResetClientAppSecretParams{
Secret: secret,
Subject: sub,
Owner: auth.ID,
})
return err
}) {
return
}
appName := "Unknown..."
if getName, ok := info.(interface{ GetName() string }); ok {
appName = getName.GetName()
}
appName := info.GetName()
h.ManageAppsGet(rw, &http.Request{
URL: &url.URL{

View File

@ -29,17 +29,18 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _
}
var role types.UserRole
var userList []database.User
var userList []database.GetUserListRow
if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(auth.ID)
role, err = tx.GetUserRole(req.Context(), auth.ID)
if err != nil {
return
}
userList, err = tx.GetUserList(offset)
userList, err = tx.GetUserList(req.Context(), int64(offset))
return
}) {
return
}
if role != types.RoleAdmin {
http.Error(rw, "403 Forbidden", http.StatusForbidden)
return
@ -55,7 +56,7 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _
}
if q.Has("edit") {
for _, i := range userList {
if i.Sub == q.Get("edit") {
if i.Subject == q.Get("edit") {
m["Edit"] = i
goto validEdit
}
@ -79,7 +80,7 @@ func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request,
var role types.UserRole
if h.DbTx(rw, func(tx *database.Queries) (err error) {
role, err = tx.GetUserRole(auth.ID)
role, err = tx.GetUserRole(req.Context(), auth.ID)
return
}) {
return
@ -116,17 +117,26 @@ func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request,
}
addrDomain := address.Address[n+1:]
var userSub uuid.UUID
var userSub string
if h.DbTx(rw, func(tx *database.Queries) (err error) {
userSub, err = tx.InsertUser(name, username, "", email, addrDomain == h.conf.Namespace, newRole, active)
userSub, err = tx.AddUser(req.Context(), database.AddUserParams{
Name: name,
Username: username,
Password: "",
Email: email,
EmailVerified: addrDomain == h.conf.Namespace,
Role: newRole,
UpdatedAt: time.Now(),
Active: active,
})
return err
}) {
return
}
u, u2 := uuid.NewString(), uuid.NewString()
h.mailLinkCache.Set(mailLinkKey{mailLinkResetPassword, u}, userSub.String(), time.Now().Add(10*time.Minute))
h.mailLinkCache.Set(mailLinkKey{mailLinkDelete, u2}, userSub.String(), time.Now().Add(10*time.Minute))
h.mailLinkCache.Set(mailLinkKey{mailLinkResetPassword, u}, userSub, time.Now().Add(10*time.Minute))
h.mailLinkCache.Set(mailLinkKey{mailLinkDelete, u2}, userSub, time.Now().Add(10*time.Minute))
err = h.conf.Mail.SendEmailTemplate("mail-register-admin", "Register", name, address, map[string]any{
"RegisterUrl": h.conf.BaseUrl + "/mail/password/" + u,
@ -139,7 +149,11 @@ func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request,
case "edit":
if h.DbTx(rw, func(tx *database.Queries) error {
sub := req.Form.Get("subject")
return tx.UpdateUser(sub, newRole, active)
return tx.UpdateUserRole(req.Context(), database.UpdateUserRoleParams{
Active: active,
Role: newRole,
Subject: sub,
})
}) {
return
}

View File

@ -77,17 +77,14 @@ func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request
}
}
var user *database.User
var user string
var hasOtp bool
if h.DbTx(rw, func(tx *database.Queries) (err error) {
user, err = tx.GetUserDisplayName(auth.ID)
if err != nil {
return
}
hasOtp, err = tx.HasTwoFactor(auth.ID)
user, err = tx.GetUserDisplayName(req.Context(), auth.ID)
if err != nil {
return
}
hasOtp, err = tx.HasOtp(req.Context(), auth.ID)
return
}) {
return

View File

@ -2,6 +2,7 @@ package server
import (
"bytes"
"context"
"encoding/base64"
"github.com/1f349/tulip/database"
"github.com/1f349/tulip/pages"
@ -45,15 +46,18 @@ func (h *HttpServer) LoginOtpPost(rw http.ResponseWriter, req *http.Request, _ h
func (h *HttpServer) fetchAndValidateOtp(rw http.ResponseWriter, sub, code string) bool {
var hasOtp bool
var otpRow database.GetOtpRow
var secret string
var digits int
var digits int64
if h.DbTx(rw, func(tx *database.Queries) (err error) {
hasOtp, err = tx.HasTwoFactor(sub)
hasOtp, err = tx.HasOtp(context.Background(), sub)
if err != nil {
return
}
if hasOtp {
secret, digits, err = tx.GetTwoFactor(sub)
otpRow, err = tx.GetOtp(context.Background(), sub)
secret = otpRow.Secret
digits = otpRow.Digits
}
return
}) {
@ -61,7 +65,7 @@ func (h *HttpServer) fetchAndValidateOtp(rw http.ResponseWriter, sub, code strin
}
if hasOtp {
totp := gotp.NewTOTP(secret, digits, 30, nil)
totp := gotp.NewTOTP(secret, int(digits), 30, nil)
if !verifyTotp(totp, code) {
http.Error(rw, "400 Bad Request: Invalid OTP code", http.StatusBadRequest)
return true
@ -87,7 +91,11 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
}
if h.DbTx(rw, func(tx *database.Queries) error {
return tx.SetTwoFactor(auth.ID, "", 0)
return tx.SetOtp(req.Context(), database.SetOtpParams{
Subject: auth.ID,
Secret: "",
Digits: 0,
})
}) {
return
}
@ -120,7 +128,7 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
var email string
if h.DbTx(rw, func(tx *database.Queries) error {
var err error
email, err = tx.GetUserEmail(auth.ID)
email, err = tx.GetUserEmail(req.Context(), auth.ID)
return err
}) {
return
@ -168,7 +176,11 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
}
if h.DbTx(rw, func(tx *database.Queries) error {
return tx.SetTwoFactor(auth.ID, secret, digits)
return tx.SetOtp(req.Context(), database.SetOtpParams{
Subject: auth.ID,
Secret: secret,
Digits: int64(digits),
})
}) {
return
}

View File

@ -191,7 +191,7 @@ func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *htt
return
}
var userData database.GetUserRow
var userData database.User
if hs.DbTx(rw, func(tx *database.Queries) (err error) {
userData, err = tx.GetUser(req.Context(), userId)
@ -212,21 +212,21 @@ func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *htt
}
if claims["profile"] {
m["profile"] = conf.BaseUrl + "/user/" + userData.Username
m["picture"] = userData.Picture.String()
m["website"] = userData.Website.String()
m["picture"] = userData.Picture
m["website"] = userData.Website
}
if claims["email"] {
m["email"] = userData.Email
m["email_verified"] = userData.EmailVerified
}
if claims["birthdate"] {
m["birthdate"] = userData.Birthdate.String()
if claims["birthdate"] && userData.Birthdate.Valid {
m["birthdate"] = userData.Birthdate.Time.String()
}
if claims["age"] {
m["age"] = CalculateAge(userData.Birthdate.Time.In(userData.ZoneInfo.Location))
m["age"] = CalculateAge(userData.Birthdate.Time.In(userData.Zoneinfo.Location))
}
if claims["zoneinfo"] {
m["zoneinfo"] = userData.ZoneInfo.Location.String()
m["zoneinfo"] = userData.Zoneinfo.Location.String()
}
if claims["locale"] {
m["locale"] = userData.Locale.Tag.String()