Pass config directly to server

This commit is contained in:
Melon 2023-10-10 18:06:43 +01:00
parent 9d9d982d7c
commit 96df1deadf
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
15 changed files with 164 additions and 73 deletions

View File

@ -55,7 +55,7 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcomm
return subcommands.ExitFailure return subcommands.ExitFailure
} }
var config startUpConfig var config server.Conf
err = json.NewDecoder(openConf).Decode(&config) err = json.NewDecoder(openConf).Decode(&config)
if err != nil { if err != nil {
log.Println("[Tulip] Error: invalid config file: ", err) log.Println("[Tulip] Error: invalid config file: ", err)
@ -71,7 +71,7 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcomm
return subcommands.ExitSuccess return subcommands.ExitSuccess
} }
func normalLoad(startUp startUpConfig, wd string) { func normalLoad(startUp server.Conf, wd string) {
key := genHmacKey() key := genHmacKey()
db, err := database.Open(filepath.Join(wd, "tulip.db.sqlite")) db, err := database.Open(filepath.Join(wd, "tulip.db.sqlite"))
@ -91,7 +91,7 @@ func normalLoad(startUp startUpConfig, wd string) {
log.Fatal("[Tulip] Failed to load mail templates:", err) log.Fatal("[Tulip] Failed to load mail templates:", err)
} }
srv := server.NewHttpServer(startUp.Listen, startUp.BaseUrl, startUp.OtpIssuer, startUp.ServiceName, startUp.Mail, db, key) srv := server.NewHttpServer(startUp, db, key)
log.Printf("[Tulip] Starting HTTP server on '%s'\n", srv.Addr) log.Printf("[Tulip] Starting HTTP server on '%s'\n", srv.Addr)
go utils.RunBackgroundHttp("HTTP", srv) go utils.RunBackgroundHttp("HTTP", srv)
@ -122,7 +122,7 @@ func checkDbHasUser(db *database.DB) error {
defer tx.Rollback() defer tx.Rollback()
if err := tx.HasUser(); err != nil { if err := tx.HasUser(); err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
_, err := tx.InsertUser("Admin", "admin", "admin", "admin@localhost", database.RoleAdmin, false) _, err := tx.InsertUser("Admin", "admin", "admin", "admin@localhost", false, database.RoleAdmin, false)
if err != nil { if err != nil {
return fmt.Errorf("failed to add user: %w", err) return fmt.Errorf("failed to add user: %w", err)
} }

View File

@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS users
locale TEXT DEFAULT "en-US" NOT NULL, locale TEXT DEFAULT "en-US" NOT NULL,
role INTEGER DEFAULT 0 NOT NULL, role INTEGER DEFAULT 0 NOT NULL,
updated_at DATETIME, updated_at DATETIME,
registered INTEGER DEFAULT 0,
active INTEGER DEFAULT 1 active INTEGER DEFAULT 1
); );

View File

@ -37,13 +37,13 @@ func (t *Tx) HasUser() error {
return nil return nil
} }
func (t *Tx) InsertUser(name, un, pw, email string, role UserRole, active bool) (uuid.UUID, error) { func (t *Tx) InsertUser(name, un, pw, email string, verifyEmail bool, role UserRole, active bool) (uuid.UUID, error) {
pwHash, err := password.HashPassword(pw) pwHash, err := password.HashPassword(pw)
if err != nil { if err != nil {
return uuid.UUID{}, err return uuid.UUID{}, err
} }
u := uuid.New() u := uuid.New()
_, err = t.tx.Exec(`INSERT INTO users (subject, name, username, password, email, role, updated_at, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, u, name, un, pwHash, email, role, updatedAt(), active) _, err = t.tx.Exec(`INSERT INTO users (subject, name, username, password, email, email_verified, role, updated_at, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, u, name, un, pwHash, email, verifyEmail, role, updatedAt(), active)
return u, err return u, err
} }
@ -235,18 +235,18 @@ func (t *Tx) InsertClientApp(name, domain string, sso, active bool, owner uuid.U
return err return err
} }
func (t *Tx) UpdateClientApp(subject uuid.UUID, name, domain string, sso, active bool) error { func (t *Tx) UpdateClientApp(subject, owner uuid.UUID, name, domain string, sso, active bool) error {
_, err := t.tx.Exec(`UPDATE client_store SET name = ?, domain = ?, sso = ?, active = ? WHERE subject = ?`, name, domain, sso, active, subject.String()) _, err := t.tx.Exec(`UPDATE client_store SET name = ?, domain = ?, sso = ?, active = ? WHERE subject = ? AND owner = ?`, name, domain, sso, active, subject.String(), owner.String())
return err return err
} }
func (t *Tx) ResetClientAppSecret(subject uuid.UUID, secret string) error { func (t *Tx) ResetClientAppSecret(subject, owner uuid.UUID) (string, error) {
secret, err := password.GenerateApiSecret(70) secret, err := password.GenerateApiSecret(70)
if err != nil { if err != nil {
return err return "", err
} }
_, err = t.tx.Exec(`UPDATE client_store SET secret = ? WHERE subject = ?`, secret, subject.String()) _, err = t.tx.Exec(`UPDATE client_store SET secret = ? WHERE subject = ? AND owner = ?`, secret, subject.String(), owner.String())
return err return secret, err
} }
func (t *Tx) GetUserList(offset int) ([]User, error) { func (t *Tx) GetUserList(offset int) ([]User, error) {

View File

@ -2,6 +2,30 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>{{.ServiceName}}</title> <title>{{.ServiceName}}</title>
<script>
window.addEventListener("load", function () {
selectText("app-secret");
});
// Thanks again: https://stackoverflow.com/a/987376
function selectText(nodeId) {
const node = document.getElementById(nodeId);
if (document.body.createTextRange) {
const range = document.body.createTextRange();
range.moveToElementText(node);
range.select();
} else if (window.getSelection) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
} else {
console.warn("Could not select text in node: Unsupported browser.");
}
}
</script>
</head> </head>
<body> <body>
<header> <header>
@ -12,6 +36,10 @@
<button type="submit">Home</button> <button type="submit">Home</button>
</form> </form>
{{if .NewAppSecret}}
<div>New application secret: <span id="app-secret">{{.NewAppSecret}}</span> for {{.NewAppName}}</div>
{{end}}
{{if .Edit}} {{if .Edit}}
<h2>Edit Client Application</h2> <h2>Edit Client Application</h2>
<form method="POST" action="/manage/apps"> <form method="POST" action="/manage/apps">
@ -31,11 +59,13 @@
</div> </div>
{{if .IsAdmin}} {{if .IsAdmin}}
<div> <div>
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso" {{if .Edit.SSO}}checked{{end}}/></label> <label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"
{{if .Edit.SSO}}checked{{end}}/></label>
</div> </div>
{{end}} {{end}}
<div> <div>
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" {{if .Edit.Active}}checked{{end}}/></label> <label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
{{if .Edit.Active}}checked{{end}}/></label>
</div> </div>
<button type="submit">Edit</button> <button type="submit">Edit</button>
</form> </form>
@ -75,6 +105,12 @@
<input type="hidden" name="edit" value="{{.Sub}}"/> <input type="hidden" name="edit" value="{{.Sub}}"/>
<button type="submit">Edit</button> <button type="submit">Edit</button>
</form> </form>
<form method="POST" action="/manage/apps?offset={{$.Offset}}">
<input type="hidden" name="action" value="secret"/>
<input type="hidden" name="offset" value="{{$.Offset}}"/>
<input type="hidden" name="subject" value="{{.Sub}}"/>
<button type="submit">Reset Secret</button>
</form>
</td> </td>
</tr> </tr>
{{end}} {{end}}
@ -100,7 +136,8 @@
</div> </div>
{{end}} {{end}}
<div> <div>
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" checked/></label> <label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
checked/></label>
</div> </div>
<button type="submit">Create</button> <button type="submit">Create</button>
</form> </form>

View File

@ -37,7 +37,8 @@
</select> </select>
</div> </div>
<div> <div>
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" checked/></label> <label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
checked/></label>
</div> </div>
<button type="submit">Edit</button> <button type="submit">Edit</button>
</form> </form>
@ -98,6 +99,10 @@
<input type="hidden" name="edit" value="{{.Sub}}"/> <input type="hidden" name="edit" value="{{.Sub}}"/>
<button type="submit">Edit</button> <button type="submit">Edit</button>
</form> </form>
<form method="POST" action="/reset-password">
<input type="hidden" name="email" value="{{.Email}}"/>
<button type="submit">Send Reset Password Email</button>
</form>
{{end}} {{end}}
</td> </td>
</tr> </tr>
@ -127,6 +132,8 @@
</div> </div>
<div> <div>
<label for="field_email">Email:</label> <label for="field_email">Email:</label>
<p>Using an `@{{.Namespace}}` email address will automatically verify as it is owned by this login
service.</p>
<input type="text" name="email" id="field_email" required/> <input type="text" name="email" id="field_email" required/>
</div> </div>
<div> <div>
@ -137,7 +144,8 @@
</select> </select>
</div> </div>
<div> <div>
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" checked/></label> <label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
checked/></label>
</div> </div>
<button type="submit">Create</button> <button type="submit">Create</button>
</form> </form>

View File

@ -1,11 +1,12 @@
package main package server
import "github.com/1f349/tulip/mail" import "github.com/1f349/tulip/mail"
type startUpConfig struct { type Conf struct {
Listen string `json:"listen"` Listen string `json:"listen"`
BaseUrl string `json:"base_url"` BaseUrl string `json:"base_url"`
OtpIssuer string `json:"otp_issuer"` OtpIssuer string `json:"otp_issuer"`
ServiceName string `json:"service_name"` ServiceName string `json:"service_name"`
Namespace string `json:"namespace"`
Mail mail.Mail `json:"mail"` Mail mail.Mail `json:"mail"`
} }

View File

@ -31,7 +31,7 @@ func (h *HttpServer) EditGet(rw http.ResponseWriter, _ *http.Request, _ httprout
return return
} }
pages.RenderPageTemplate(rw, "edit", map[string]any{ pages.RenderPageTemplate(rw, "edit", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"User": user, "User": user,
"Nonce": lNonce, "Nonce": lNonce,
"FieldPronoun": user.Pronouns.String(), "FieldPronoun": user.Pronouns.String(),

View File

@ -14,7 +14,7 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
if auth.IsGuest() { if auth.IsGuest() {
pages.RenderPageTemplate(rw, "index-guest", map[string]any{ pages.RenderPageTemplate(rw, "index-guest", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
}) })
return return
} }
@ -37,7 +37,7 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
return return
} }
pages.RenderPageTemplate(rw, "index", map[string]any{ pages.RenderPageTemplate(rw, "index", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"Auth": auth, "Auth": auth,
"User": userWithName, "User": userWithName,
"Nonce": lNonce, "Nonce": lNonce,

View File

@ -43,7 +43,7 @@ func (h *HttpServer) LoginGet(rw http.ResponseWriter, req *http.Request, _ httpr
rw.Header().Set("Content-Type", "text/html") rw.Header().Set("Content-Type", "text/html")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
pages.RenderPageTemplate(rw, "login", map[string]any{ pages.RenderPageTemplate(rw, "login", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"Redirect": req.URL.Query().Get("redirect"), "Redirect": req.URL.Query().Get("redirect"),
"Mismatch": req.URL.Query().Get("mismatch"), "Mismatch": req.URL.Query().Get("mismatch"),
"LoginName": loginName, "LoginName": loginName,
@ -100,8 +100,8 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
h.mailLinkCache.Set(mailLinkKey{mailLinkVerifyEmail, u}, userInfo.Sub, time.Now().Add(10*time.Minute)) h.mailLinkCache.Set(mailLinkKey{mailLinkVerifyEmail, u}, userInfo.Sub, time.Now().Add(10*time.Minute))
// try to send email // try to send email
err = h.mailer.SendEmailTemplate("mail-verify", "Verify Email", userInfo.Name, address, map[string]any{ err = h.conf.Mail.SendEmailTemplate("mail-verify", "Verify Email", userInfo.Name, address, map[string]any{
"VerifyUrl": h.domain + "/mail/verify/" + u.String(), "VerifyUrl": h.conf.BaseUrl + "/mail/verify/" + u.String(),
}) })
if err != nil { if err != nil {
log.Println("[Tulip] Login: Failed to send verification email:", err) log.Println("[Tulip] Login: Failed to send verification email:", err)

View File

@ -68,7 +68,7 @@ func (h *HttpServer) MailPassword(rw http.ResponseWriter, req *http.Request, par
} }
pages.RenderPageTemplate(rw, "reset-password", map[string]any{ pages.RenderPageTemplate(rw, "reset-password", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
}) })
} }
@ -155,7 +155,7 @@ func (h *HttpServer) MailDelete(rw http.ResponseWriter, req *http.Request, param
return return
} }
err = h.mailer.SendEmailTemplate("mail-account-delete", "Account Deletion", userInfo.Name, address, nil) err = h.conf.Mail.SendEmailTemplate("mail-account-delete", "Account Deletion", userInfo.Name, address, nil)
if err != nil { if err != nil {
http.Error(rw, "Failed to send confirmation email.", http.StatusInternalServerError) http.Error(rw, "Failed to send confirmation email.", http.StatusInternalServerError)
return return

View File

@ -3,6 +3,7 @@ package server
import ( import (
"github.com/1f349/tulip/database" "github.com/1f349/tulip/database"
"github.com/1f349/tulip/pages" "github.com/1f349/tulip/pages"
"github.com/go-oauth2/oauth2/v4"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"net/http" "net/http"
@ -36,10 +37,12 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
} }
m := map[string]any{ m := map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"Apps": appList, "Apps": appList,
"Offset": offset, "Offset": offset,
"IsAdmin": role == database.RoleAdmin, "IsAdmin": role == database.RoleAdmin,
"NewAppName": q.Get("NewAppName"),
"NewAppSecret": q.Get("NewAppSecret"),
} }
if q.Has("edit") { if q.Has("edit") {
for _, i := range appList { for _, i := range appList {
@ -99,10 +102,43 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
if err != nil { if err != nil {
return err return err
} }
return tx.UpdateClientApp(sub, name, domain, sso, active) return tx.UpdateClientApp(sub, auth.Data.ID, name, domain, sso, active)
}) { }) {
return return
} }
case "secret":
var info oauth2.ClientInfo
var secret string
if h.DbTx(rw, func(tx *database.Tx) error {
sub, err := uuid.Parse(req.Form.Get("subject"))
if err != nil {
return err
}
info, err = tx.GetClientInfo(sub.String())
if err != nil {
return err
}
secret, err = tx.ResetClientAppSecret(sub, auth.Data.ID)
return err
}) {
return
}
appName := "Unknown..."
if getName, ok := info.(interface{ GetName() string }); ok {
appName = getName.GetName()
}
h.ManageAppsGet(rw, &http.Request{
URL: &url.URL{
RawQuery: url.Values{
"offset": []string{offset},
"NewAppName": []string{appName},
"NewAppSecret": []string{secret},
}.Encode(),
},
}, httprouter.Params{}, auth)
return
default: default:
http.Error(rw, "400 Bad Request: Invalid action", http.StatusBadRequest) http.Error(rw, "400 Bad Request: Invalid action", http.StatusBadRequest)
return return

View File

@ -11,6 +11,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"time" "time"
) )
@ -44,11 +45,12 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _
} }
m := map[string]any{ m := map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"Users": userList, "Users": userList,
"Offset": offset, "Offset": offset,
"EmailShow": req.URL.Query().Has("show-email"), "EmailShow": req.URL.Query().Has("show-email"),
"CurrentAdmin": auth.Data.ID, "CurrentAdmin": auth.Data.ID,
"Namespace": h.conf.Namespace,
} }
if q.Has("edit") { if q.Has("edit") {
for _, i := range userList { for _, i := range userList {
@ -100,28 +102,33 @@ func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request,
switch action { switch action {
case "create": case "create":
var userSub uuid.UUID
if h.DbTx(rw, func(tx *database.Tx) (err error) {
userSub, err = tx.InsertUser(name, username, "", email, newRole, active)
return err
}) {
return
}
// parse email for headers // parse email for headers
address, err := mail.ParseAddress(email) address, err := mail.ParseAddress(email)
if err != nil { if err != nil {
http.Error(rw, "500 Internal Server Error: Failed to parse user email address", http.StatusInternalServerError) http.Error(rw, "500 Internal Server Error: Failed to parse user email address", http.StatusInternalServerError)
return return
} }
n := strings.IndexByte(address.Address, '@')
// This case should never happen and fail the above address parsing
if n == -1 {
return
}
addrDomain := address.Address[n+1:]
var userSub uuid.UUID
if h.DbTx(rw, func(tx *database.Tx) (err error) {
userSub, err = tx.InsertUser(name, username, "", email, addrDomain == h.conf.Namespace, newRole, active)
return err
}) {
return
}
u, u2 := uuid.New(), uuid.New() u, u2 := uuid.New(), uuid.New()
h.mailLinkCache.Set(mailLinkKey{mailLinkResetPassword, u}, userSub, 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)) h.mailLinkCache.Set(mailLinkKey{mailLinkDelete, u2}, userSub, time.Now().Add(10*time.Minute))
err = h.mailer.SendEmailTemplate("mail-register-delete", "Register", name, address, map[string]any{ err = h.conf.Mail.SendEmailTemplate("mail-register-admin", "Register", name, address, map[string]any{
"ResetUrl": h.domain + "/mail/password/" + u.String(), "RegisterUrl": h.conf.BaseUrl + "/mail/password/" + u.String(),
"DeleteUrl": h.domain + "/mail/delete/" + u2.String(),
}) })
if err != nil { if err != nil {
log.Println("[Tulip] Login: Failed to send register email:", err) log.Println("[Tulip] Login: Failed to send register email:", err)

View File

@ -95,7 +95,7 @@ func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
pages.RenderPageTemplate(rw, "oauth-authorize", map[string]any{ pages.RenderPageTemplate(rw, "oauth-authorize", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"AppName": appName, "AppName": appName,
"AppDomain": appDomain, "AppDomain": appDomain,
"User": user, "User": user,

View File

@ -19,7 +19,7 @@ func (h *HttpServer) LoginOtpGet(rw http.ResponseWriter, req *http.Request, _ ht
} }
pages.RenderPageTemplate(rw, "login-otp", map[string]any{ pages.RenderPageTemplate(rw, "login-otp", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"Redirect": req.URL.Query().Get("redirect"), "Redirect": req.URL.Query().Get("redirect"),
}) })
} }
@ -53,7 +53,7 @@ func (h *HttpServer) fetchAndValidateOtp(rw http.ResponseWriter, sub uuid.UUID,
return return
} }
if hasOtp { if hasOtp {
otp, err = tx.GetTwoFactor(sub, h.otpIssuer) otp, err = tx.GetTwoFactor(sub, h.conf.OtpIssuer)
} }
return return
}) { }) {
@ -121,7 +121,7 @@ func (h *HttpServer) EditOtpGet(rw http.ResponseWriter, req *http.Request, _ htt
// generate OTP key // generate OTP key
var err error var err error
otp, err = twofactor.NewTOTP(email, h.otpIssuer, crypto.SHA512, digits) otp, err = twofactor.NewTOTP(email, h.conf.OtpIssuer, crypto.SHA512, digits)
if err != nil { if err != nil {
http.Error(rw, "500 Internal Server Error: Failed to generate OTP key", http.StatusInternalServerError) http.Error(rw, "500 Internal Server Error: Failed to generate OTP key", http.StatusInternalServerError)
return return
@ -150,7 +150,7 @@ func (h *HttpServer) EditOtpGet(rw http.ResponseWriter, req *http.Request, _ htt
// render page // render page
pages.RenderPageTemplate(rw, "edit-otp", map[string]any{ pages.RenderPageTemplate(rw, "edit-otp", map[string]any{
"ServiceName": h.serviceName, "ServiceName": h.conf.ServiceName,
"OtpQr": template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(otpQr)), "OtpQr": template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(otpQr)),
"OtpUrl": otpUrl, "OtpUrl": otpUrl,
}) })

View File

@ -8,7 +8,6 @@ import (
"github.com/1f349/cache" "github.com/1f349/cache"
clientStore "github.com/1f349/tulip/client-store" clientStore "github.com/1f349/tulip/client-store"
"github.com/1f349/tulip/database" "github.com/1f349/tulip/database"
"github.com/1f349/tulip/mail"
"github.com/1f349/tulip/openid" "github.com/1f349/tulip/openid"
scope2 "github.com/1f349/tulip/scope" scope2 "github.com/1f349/tulip/scope"
"github.com/go-oauth2/oauth2/v4/errors" "github.com/go-oauth2/oauth2/v4/errors"
@ -28,15 +27,12 @@ import (
var errInvalidScope = errors.New("missing required scope") var errInvalidScope = errors.New("missing required scope")
type HttpServer struct { type HttpServer struct {
r *httprouter.Router r *httprouter.Router
oauthSrv *server.Server oauthSrv *server.Server
oauthMgr *manage.Manager oauthMgr *manage.Manager
db *database.DB db *database.DB
domain string conf Conf
privKey []byte privKey []byte
otpIssuer string
serviceName string
mailer mail.Mail
// mailLinkCache contains a mapping of verify uuids to user uuids // mailLinkCache contains a mapping of verify uuids to user uuids
mailLinkCache *cache.Cache[mailLinkKey, uuid.UUID] mailLinkCache *cache.Cache[mailLinkKey, uuid.UUID]
@ -71,10 +67,18 @@ func (h *HttpServer) SafeRedirect(rw http.ResponseWriter, req *http.Request) {
http.Redirect(rw, req, parse.String(), http.StatusFound) http.Redirect(rw, req, parse.String(), http.StatusFound)
} }
func NewHttpServer(listen, domain, otpIssuer, serviceName string, mailer mail.Mail, db *database.DB, privKey []byte) *http.Server { func NewHttpServer(conf Conf, db *database.DB, privKey []byte) *http.Server {
r := httprouter.New() r := httprouter.New()
openIdConf := openid.GenConfig(domain, []string{"openid", "name", "username", "profile", "email", "birthdate", "age", "zoneinfo", "locale"}, []string{"sub", "name", "preferred_username", "profile", "picture", "website", "email", "email_verified", "gender", "birthdate", "zoneinfo", "locale", "updated_at"}) // remove last slash from baseUrl
{
l := len(conf.BaseUrl)
if conf.BaseUrl[l-1] == '/' {
conf.BaseUrl = conf.BaseUrl[:l-1]
}
}
openIdConf := openid.GenConfig(conf.BaseUrl, []string{"openid", "name", "username", "profile", "email", "birthdate", "age", "zoneinfo", "locale"}, []string{"sub", "name", "preferred_username", "profile", "picture", "website", "email", "email_verified", "gender", "birthdate", "zoneinfo", "locale", "updated_at"})
openIdBytes, err := json.Marshal(openIdConf) openIdBytes, err := json.Marshal(openIdConf)
if err != nil { if err != nil {
log.Fatalln("Failed to generate OpenID configuration:", err) log.Fatalln("Failed to generate OpenID configuration:", err)
@ -83,15 +87,12 @@ func NewHttpServer(listen, domain, otpIssuer, serviceName string, mailer mail.Ma
oauthManager := manage.NewDefaultManager() oauthManager := manage.NewDefaultManager()
oauthSrv := server.NewServer(server.NewConfig(), oauthManager) oauthSrv := server.NewServer(server.NewConfig(), oauthManager)
hs := &HttpServer{ hs := &HttpServer{
r: httprouter.New(), r: httprouter.New(),
oauthSrv: oauthSrv, oauthSrv: oauthSrv,
oauthMgr: oauthManager, oauthMgr: oauthManager,
db: db, db: db,
domain: domain, conf: conf,
privKey: privKey, privKey: privKey,
otpIssuer: otpIssuer,
serviceName: serviceName,
mailer: mailer,
mailLinkCache: cache.New[mailLinkKey, uuid.UUID](), mailLinkCache: cache.New[mailLinkKey, uuid.UUID](),
} }
@ -220,10 +221,10 @@ func NewHttpServer(listen, domain, otpIssuer, serviceName string, mailer mail.Ma
m["name"] = userData.Name m["name"] = userData.Name
} }
if claims["username"] { if claims["username"] {
m["preferred_username"] = userData.Name m["preferred_username"] = userData.Username
} }
if claims["profile"] { if claims["profile"] {
m["profile"] = domain + "/user/" + userData.Username m["profile"] = conf.BaseUrl + "/user/" + userData.Username
m["picture"] = userData.Picture.String() m["picture"] = userData.Picture.String()
m["website"] = userData.Website.String() m["website"] = userData.Website.String()
} }
@ -249,7 +250,7 @@ func NewHttpServer(listen, domain, otpIssuer, serviceName string, mailer mail.Ma
}) })
return &http.Server{ return &http.Server{
Addr: listen, Addr: conf.Listen,
Handler: r, Handler: r,
ReadTimeout: time.Minute, ReadTimeout: time.Minute,
ReadHeaderTimeout: time.Minute, ReadHeaderTimeout: time.Minute,