Implement initial login functionality
This commit is contained in:
parent
a3bd0db78c
commit
3aa79b0d23
36 changed files with 908 additions and 70 deletions
23
internal/dis/component/auth/auth.go
Normal file
23
internal/dis/component/auth/auth.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
component.Base
|
||||
Dependencies struct {
|
||||
SQL *sql.SQL
|
||||
}
|
||||
|
||||
storeOnce sync.Once
|
||||
store sessions.Store
|
||||
}
|
||||
|
||||
var (
|
||||
_ component.Routeable = (*Auth)(nil)
|
||||
)
|
||||
31
internal/dis/component/auth/templates/login.html
Normal file
31
internal/dis/component/auth/templates/login.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{{ template "_base.html" . }}
|
||||
{{ define "title" }}Login{{ end }}
|
||||
|
||||
{{ define "header/time" }}
|
||||
<!-- no header/time -->
|
||||
{{ end }}
|
||||
{{ define "header"}}
|
||||
<!-- no header -->
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="pure-u-1">
|
||||
|
||||
{{ if .Message }}
|
||||
<div>
|
||||
{{ .Message }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<form class="pure-form" method="POST">
|
||||
<fieldset>
|
||||
<legend>Login Required</legend>
|
||||
|
||||
<input type="text" name="username">
|
||||
<input type="password" name="password">
|
||||
<input type="submit" value="Login">
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
176
internal/dis/component/auth/user.go
Normal file
176
internal/dis/component/auth/user.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// ErrUserNotFound is returned when a user is not found
|
||||
var ErrUserNotFound = errors.New("user not found")
|
||||
|
||||
// Users returns all users in the database
|
||||
func (auth *Auth) Users(ctx context.Context) (users []*AuthUser, err error) {
|
||||
// query the user table
|
||||
table, err := auth.Dependencies.SQL.QueryTable(ctx, false, models.UserTable)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// find all the users
|
||||
var dUsers []models.User
|
||||
err = table.Find(&dUsers).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// and map them to high-level user objects
|
||||
users = make([]*AuthUser, len(dUsers))
|
||||
for i, user := range dUsers {
|
||||
users[i] = &AuthUser{
|
||||
User: user,
|
||||
auth: auth,
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// User returns a single user.
|
||||
// If the user does not exist, returns ErrUserNotFound.
|
||||
func (auth *Auth) User(ctx context.Context, name string) (user *AuthUser, err error) {
|
||||
// quick and dirty check for the empty username (which is not allowed)
|
||||
if name == "" {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
// return the user
|
||||
table, err := auth.Dependencies.SQL.QueryTable(ctx, false, models.UserTable)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user = &AuthUser{}
|
||||
|
||||
// find the user
|
||||
res := table.Where(&models.User{User: name}).Find(&user.User)
|
||||
err = res.Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// check if the user was not found
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
user.auth = auth
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateUser creates a new user and returns it.
|
||||
// The user is not associated to any WissKIs, and has no password set.
|
||||
func (auth *Auth) CreateUser(ctx context.Context, name string) (user *AuthUser, err error) {
|
||||
// return the user
|
||||
table, err := auth.Dependencies.SQL.QueryTable(ctx, false, models.UserTable)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user = &AuthUser{
|
||||
User: models.User{
|
||||
User: name,
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
// do the create statement
|
||||
err = table.Create(&user.User).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.auth = auth
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// AuthUser represents an authorized user
|
||||
type AuthUser struct {
|
||||
auth *Auth
|
||||
models.User
|
||||
}
|
||||
|
||||
func (au AuthUser) String() string {
|
||||
hasPassword := len(au.PasswordHash) > 0
|
||||
return fmt.Sprintf("User{Name:%q,Enabled:%t,HasPassword:%t,Admin:%t}", au.User.User, au.User.Enabled, hasPassword, au.User.Admin)
|
||||
}
|
||||
|
||||
// SetPassword sets the password for this user and turns the user on
|
||||
func (au *AuthUser) SetPassword(ctx context.Context, password []byte) (err error) {
|
||||
au.User.PasswordHash, err = bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
au.User.Enabled = true
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
// UnsetPassword removes the password from this user, and disables them
|
||||
func (au *AuthUser) UnsetPassword(ctx context.Context) error {
|
||||
au.User.PasswordHash = nil
|
||||
au.User.Enabled = false
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
var ErrUserDisabled = errors.New("user is disabled")
|
||||
var ErrUserBlank = errors.New("user has no password set")
|
||||
|
||||
// CheckPassword checks if this user can login with the provided password.
|
||||
// Returns nil on success, an error otherwise.
|
||||
func (au *AuthUser) CheckPassword(ctx context.Context, password []byte) error {
|
||||
if !au.User.Enabled {
|
||||
return ErrUserDisabled
|
||||
}
|
||||
|
||||
if len(au.User.PasswordHash) == 0 {
|
||||
return ErrUserDisabled
|
||||
}
|
||||
|
||||
return bcrypt.CompareHashAndPassword(au.User.PasswordHash, password)
|
||||
}
|
||||
|
||||
// MakeAdmin makes this user an admin, and saves the update in the database.
|
||||
// If the user is already an admin, does not return an error.
|
||||
func (au *AuthUser) MakeAdmin(ctx context.Context) error {
|
||||
au.User.Admin = true
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
// MakeRegular removes admin rights from this user.
|
||||
// If this user is not an dmin, does not return an error.
|
||||
func (au *AuthUser) MakeRegular(ctx context.Context) error {
|
||||
au.User.Admin = true
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
// Save saves the given user in the database
|
||||
func (au *AuthUser) Save(ctx context.Context) error {
|
||||
table, err := au.auth.Dependencies.SQL.QueryTable(ctx, false, models.UserTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return table.Save(&au.User).Error
|
||||
}
|
||||
|
||||
// Delete deletes the user from the database
|
||||
func (au *AuthUser) Delete(ctx context.Context) error {
|
||||
table, err := au.auth.Dependencies.SQL.QueryTable(ctx, false, models.UserTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return table.Delete(&au.User).Error
|
||||
}
|
||||
260
internal/dis/component/auth/web.go
Normal file
260
internal/dis/component/auth/web.go
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
func (auth *Auth) Routes() []string {
|
||||
return []string{"/auth/"}
|
||||
}
|
||||
|
||||
type contextUserKey struct{}
|
||||
|
||||
var ctxUserKey = contextUserKey{}
|
||||
|
||||
const (
|
||||
sessionCookieName = "distillery-session"
|
||||
sessionUserKey = "user"
|
||||
)
|
||||
|
||||
// session returns the session belonging to a request
|
||||
func (auth *Auth) session(r *http.Request) (*sessions.Session, error) {
|
||||
auth.storeOnce.Do(func() {
|
||||
auth.store = sessions.NewCookieStore([]byte(auth.Config.SessionSecret))
|
||||
})
|
||||
return auth.store.Get(r, sessionCookieName)
|
||||
}
|
||||
|
||||
// UserOf returns the user logged into the given request.
|
||||
// If there is no user associated with the given user, user and error will be nil.
|
||||
//
|
||||
// When no UserOf exists in the given session returns nil.
|
||||
// An invalid session (for a UserOf)
|
||||
func (auth *Auth) UserOf(r *http.Request) (user *AuthUser, err error) {
|
||||
ctx := r.Context()
|
||||
if user, ok := ctx.Value(ctxUserKey).(*AuthUser); ok && user != nil {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// first read the session
|
||||
sess, err := auth.session(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to read the name from the session
|
||||
name, ok := sess.Values[sessionUserKey]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
nameS, ok := name.(string)
|
||||
if !ok || nameS == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// fetch the user, check if they still exist
|
||||
user, err = auth.User(ctx, nameS)
|
||||
if err == ErrUserNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// user isn't enabled
|
||||
if !user.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// get the user
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// writeLogin marks the user as logged in on the given writer
|
||||
func (auth *Auth) writeLogin(w http.ResponseWriter, r *http.Request, user *AuthUser) error {
|
||||
sess, err := auth.session(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Values[sessionUserKey] = user.User.User
|
||||
return sess.Save(r, w)
|
||||
}
|
||||
|
||||
// writeLogout logs out the user form the given session
|
||||
func (auth *Auth) writeLogout(w http.ResponseWriter, r *http.Request) error {
|
||||
sess, err := auth.session(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Options.MaxAge = -1
|
||||
return sess.Save(r, w)
|
||||
}
|
||||
|
||||
//go:embed "templates/login.html"
|
||||
var loginHTMLStr string
|
||||
var loginTemplate = static.AssetsAuthLogin.MustParseShared("login.html", loginHTMLStr)
|
||||
|
||||
var loginResponse = httpx.Response{
|
||||
ContentType: "text/plain",
|
||||
Body: []byte("user is signed in"),
|
||||
}
|
||||
|
||||
// HandleRoute returns the handler for the requested route
|
||||
func (auth *Auth) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||
router := httprouter.New()
|
||||
|
||||
router.Handler(http.MethodGet, route, auth.Protect(loginResponse, nil))
|
||||
|
||||
router.HandlerFunc(http.MethodGet, route+"login", auth.loginRoute)
|
||||
router.HandlerFunc(http.MethodPost, route+"login", auth.loginRoute)
|
||||
|
||||
router.HandlerFunc(http.MethodGet, route+"logout", auth.logoutRoute)
|
||||
|
||||
return router, nil
|
||||
}
|
||||
|
||||
type loginContext struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// Protect returns a new handler which requires a user to be logged in and pass the perm function.
|
||||
//
|
||||
// If an unauthenticated user attempts to access the returned handler, they are redirected to the login endpoint.
|
||||
// When a user is logged in, and they pass the perm function (or the perm function is nil), the original handler is called.
|
||||
func (auth *Auth) Protect(handler http.Handler, perm func(user *AuthUser, r *http.Request) (ok bool, err error)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// load the user in the session
|
||||
user, err := auth.UserOf(r)
|
||||
if err != nil {
|
||||
goto err
|
||||
}
|
||||
|
||||
// if there is no user in the session
|
||||
// we need to login the user
|
||||
if user == nil {
|
||||
|
||||
// we can't redirect anything other than GET
|
||||
// (because it might be a form)
|
||||
// => so we just return a forbidden
|
||||
if r.Method != http.MethodGet {
|
||||
goto forbidden
|
||||
}
|
||||
|
||||
// redirect the user to the login endpoint, with the original URI as a return
|
||||
dest := "/auth/login?next=" + url.QueryEscape(r.URL.RequestURI())
|
||||
http.Redirect(w, r, dest, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// if we have a permission check, we need to call it
|
||||
// to find out if the user is actually allowed to access the page
|
||||
if perm != nil {
|
||||
ok, err := perm(user, r)
|
||||
if err != nil {
|
||||
goto err
|
||||
}
|
||||
if !ok {
|
||||
goto forbidden
|
||||
}
|
||||
}
|
||||
|
||||
// store the user into the session
|
||||
r = r.WithContext(context.WithValue(r.Context(), ctxUserKey, user))
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
forbidden:
|
||||
httpx.HTMLInterceptor.Intercept(w, r, httpx.ErrForbidden)
|
||||
return
|
||||
err:
|
||||
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (auth *Auth) loginRoute(w http.ResponseWriter, r *http.Request) {
|
||||
var message string
|
||||
|
||||
// try to read a user from the session
|
||||
user, err := auth.UserOf(r)
|
||||
if err != nil {
|
||||
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
goto success
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
default:
|
||||
panic("never reached")
|
||||
case http.MethodGet:
|
||||
goto form
|
||||
case http.MethodPost:
|
||||
// parse the form!
|
||||
if err := r.ParseForm(); err != nil {
|
||||
message = "Login failed"
|
||||
goto form
|
||||
}
|
||||
|
||||
// get the username and password
|
||||
username := r.Form.Get("username")
|
||||
password := r.Form.Get("password")
|
||||
|
||||
// make sure that the user exists
|
||||
user, err := auth.User(r.Context(), username)
|
||||
if err != nil {
|
||||
message = "Login failed"
|
||||
goto form
|
||||
}
|
||||
|
||||
// check the password (TODO: Support TOTP)
|
||||
err = user.CheckPassword(r.Context(), []byte(password))
|
||||
if err != nil {
|
||||
message = "Login failed"
|
||||
goto form
|
||||
}
|
||||
|
||||
// and we logged the user in!
|
||||
auth.writeLogin(w, r, user)
|
||||
goto success
|
||||
}
|
||||
|
||||
form:
|
||||
httpx.WriteHTML(loginContext{
|
||||
Message: message,
|
||||
}, nil, loginTemplate, "", w, r)
|
||||
return
|
||||
success:
|
||||
// get the destination
|
||||
next := r.URL.Query().Get("next")
|
||||
if next == "" || next[0] != '/' {
|
||||
next = "/"
|
||||
}
|
||||
|
||||
// and redirect to it!
|
||||
http.Redirect(w, r, next, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (auth *Auth) logoutRoute(w http.ResponseWriter, r *http.Request) {
|
||||
// do the logout
|
||||
auth.writeLogout(w, r)
|
||||
|
||||
// get the destination
|
||||
next := r.URL.Query().Get("next")
|
||||
if next == "" || next[0] != '/' {
|
||||
next = "/"
|
||||
}
|
||||
|
||||
// and redirect to it!
|
||||
http.Redirect(w, r, next, http.StatusSeeOther)
|
||||
|
||||
}
|
||||
|
|
@ -16,8 +16,8 @@ import (
|
|||
type Control struct {
|
||||
component.Base
|
||||
Dependencies struct {
|
||||
Servables []component.Servable
|
||||
Cronables []component.Cronable
|
||||
Routeables []component.Routeable
|
||||
Cronables []component.Cronable
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ type Home struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ component.Servable = (*Home)(nil)
|
||||
_ component.Routeable = (*Home)(nil)
|
||||
)
|
||||
|
||||
func (*Home) Routes() []string { return []string{"/"} }
|
||||
|
||||
func (home *Home) Handler(ctx context.Context, route string) (http.Handler, error) {
|
||||
func (home *Home) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ type Info struct {
|
|||
|
||||
var (
|
||||
_ component.DistilleryFetcher = (*Info)(nil)
|
||||
_ component.Servable = (*Info)(nil)
|
||||
_ component.Routeable = (*Info)(nil)
|
||||
)
|
||||
|
||||
func (*Info) Routes() []string { return []string{"/dis/"} }
|
||||
|
||||
func (info *Info) Handler(ctx context.Context, route string) (handler http.Handler, err error) {
|
||||
func (info *Info) HandleRoute(ctx context.Context, route string) (handler http.Handler, err error) {
|
||||
|
||||
router := httprouter.New()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/cancel"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
|
|
@ -12,20 +13,24 @@ import (
|
|||
// The server may spawn background tasks, but these should be terminated once context closes.
|
||||
//
|
||||
// Logging messages are directed to progress
|
||||
func (control *Control) Server(ctx context.Context, progress io.Writer) (*http.ServeMux, error) {
|
||||
func (control *Control) Server(ctx context.Context, progress io.Writer) (http.Handler, error) {
|
||||
// create a new mux
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// add all the servable routes!
|
||||
for _, s := range control.Dependencies.Servables {
|
||||
for _, s := range control.Dependencies.Routeables {
|
||||
for _, route := range s.Routes() {
|
||||
zerolog.Ctx(ctx).Info().Str("component", s.Name()).Str("route", route).Msg("mounting route")
|
||||
handler, err := s.Handler(ctx, route)
|
||||
handler, err := s.HandleRoute(ctx, route)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mux.Handle(route, handler)
|
||||
}
|
||||
}
|
||||
return mux, nil
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(cancel.ValuesOf(r.Context(), ctx))
|
||||
mux.ServeHTTP(w, r)
|
||||
}), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type Assets struct {
|
|||
Styles string // <link> tags inserted by the asset
|
||||
}
|
||||
|
||||
//go:generate node build.mjs HomeHome ComponentsIndex ControlIndex ControlInstance InstanceComponentsIndex
|
||||
//go:generate node build.mjs HomeHome ComponentsIndex ControlIndex ControlInstance InstanceComponentsIndex AuthLogin
|
||||
|
||||
// MustParse parses a new template from the given source
|
||||
// and calls [RegisterAssoc] on it.
|
||||
|
|
|
|||
|
|
@ -31,3 +31,9 @@ var AssetsInstanceComponentsIndex = Assets{
|
|||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/InstanceComponentsIndex.38d394c2.js"></script><script src="/static/InstanceComponentsIndex.38d394c2.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/InstanceComponentsIndex.38d394c2.css">`,
|
||||
}
|
||||
|
||||
// AssetsAuthLogin contains assets for the 'AuthLogin' entrypoint.
|
||||
var AssetsAuthLogin = Assets{
|
||||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/AuthLogin.38d394c2.js"></script><script src="/static/AuthLogin.38d394c2.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/AuthLogin.38d394c2.css">`,
|
||||
}
|
||||
|
|
|
|||
0
internal/dis/component/control/static/dist/AuthLogin.38d394c2.css
vendored
Normal file
0
internal/dis/component/control/static/dist/AuthLogin.38d394c2.css
vendored
Normal file
0
internal/dis/component/control/static/dist/AuthLogin.38d394c2.js
vendored
Normal file
0
internal/dis/component/control/static/dist/AuthLogin.38d394c2.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/* nothing for now */
|
||||
|
|
@ -0,0 +1 @@
|
|||
// nothing for now
|
||||
|
|
@ -15,7 +15,7 @@ type Static struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ component.Servable = (*Static)(nil)
|
||||
_ component.Routeable = (*Static)(nil)
|
||||
)
|
||||
|
||||
func (*Static) Routes() []string { return []string{"/static/"} }
|
||||
|
|
@ -23,7 +23,7 @@ func (*Static) Routes() []string { return []string{"/static/"} }
|
|||
//go:embed dist
|
||||
var staticFS embed.FS
|
||||
|
||||
func (static *Static) Handler(ctx context.Context, route string) (http.Handler, error) {
|
||||
func (static *Static) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||
// take the filesystem
|
||||
fs, err := fs.Sub(staticFS, "dist")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -28,13 +28,15 @@ type Resolver struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ component.Servable = (*Resolver)(nil)
|
||||
_ component.Cronable = (*Resolver)(nil)
|
||||
_ component.Routeable = (*Resolver)(nil)
|
||||
_ component.Cronable = (*Resolver)(nil)
|
||||
)
|
||||
|
||||
func (resolver *Resolver) Routes() []string { return []string{"/go/", "/wisski/get/"} }
|
||||
|
||||
func (resolver *Resolver) Handler(ctx context.Context, route string) (http.Handler, error) {
|
||||
func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||
logger := zerolog.Ctx(ctx)
|
||||
|
||||
var err error
|
||||
return resolver.handler.Get(func() (p wdresolve.ResolveHandler) {
|
||||
p.TrustXForwardedProto = true
|
||||
|
|
@ -47,13 +49,13 @@ func (resolver *Resolver) Handler(ctx context.Context, route string) (http.Handl
|
|||
domainName := resolver.Config.DefaultDomain
|
||||
if domainName != "" {
|
||||
fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domainName))] = fmt.Sprintf("https://$1.%s", domainName)
|
||||
zerolog.Ctx(ctx).Info().Str("name", domainName).Msg("registering default domain")
|
||||
logger.Info().Str("name", domainName).Msg("registering default domain")
|
||||
}
|
||||
|
||||
// handle the extra domains!
|
||||
for _, domain := range resolver.Config.SelfExtraDomains {
|
||||
fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domain))] = fmt.Sprintf("https://$1.%s", domainName)
|
||||
zerolog.Ctx(ctx).Info().Str("name", domainName).Msg("registering legacy domain")
|
||||
logger.Info().Str("name", domainName).Msg("registering legacy domain")
|
||||
}
|
||||
|
||||
// resolve the prefixes
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
// Servable is a component that is servable
|
||||
type Servable interface {
|
||||
// Routeable is a component that is servable
|
||||
type Routeable interface {
|
||||
Component
|
||||
|
||||
// Routes returns the routes served by this servable
|
||||
Routes() []string
|
||||
|
||||
// Handler returns the handler for the requested route
|
||||
Handler(ctx context.Context, route string) (http.Handler, error)
|
||||
// HandleRoute returns the handler for the requested route
|
||||
HandleRoute(ctx context.Context, route string) (http.Handler, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@ func (sql *SQL) Update(ctx context.Context, progress io.Writer) error {
|
|||
&models.Lock{},
|
||||
models.LockTable,
|
||||
},
|
||||
{
|
||||
"users",
|
||||
&models.User{},
|
||||
models.UserTable,
|
||||
},
|
||||
}
|
||||
|
||||
// migrate all of the tables!
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue