mirror of
https://github.com/1f349/tulip.git
synced 2025-01-02 21:46:24 +00:00
Update to make custom themes easier
This commit is contained in:
parent
3555742316
commit
9bea6805d5
68
pages/manage-apps-create.go.html
Normal file
68
pages/manage-apps-create.go.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{.ServiceName}}</title>
|
||||
<link rel="stylesheet" href="/theme/style.css">
|
||||
<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>
|
||||
<body>
|
||||
<header>
|
||||
<h1>{{.ServiceName}}</h1>
|
||||
</header>
|
||||
<main>
|
||||
<form method="GET" action="/">
|
||||
<button type="submit">Home</button>
|
||||
</form>
|
||||
|
||||
<h2>Create Client Application</h2>
|
||||
<form method="POST" action="/manage/apps">
|
||||
<input type="hidden" name="action" value="create"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_domain">Domain:</label>
|
||||
<input type="text" name="domain" id="field_domain" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public"/></label>
|
||||
</div>
|
||||
{{if .IsAdmin}}
|
||||
<div>
|
||||
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"/></label>
|
||||
</div>
|
||||
{{end}}
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
||||
checked/></label>
|
||||
</div>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
75
pages/manage-apps-edit.go.html
Normal file
75
pages/manage-apps-edit.go.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{.ServiceName}}</title>
|
||||
<link rel="stylesheet" href="/theme/style.css">
|
||||
<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>
|
||||
<body>
|
||||
<header>
|
||||
<h1>{{.ServiceName}}</h1>
|
||||
</header>
|
||||
<main>
|
||||
<form method="GET" action="/">
|
||||
<button type="submit">Home</button>
|
||||
</form>
|
||||
|
||||
<h2>Edit Client Application</h2>
|
||||
<form method="POST" action="/manage/apps">
|
||||
<input type="hidden" name="action" value="edit"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<input type="hidden" name="subject" value="{{.EditApp.Subject}}"/>
|
||||
<div>
|
||||
<label>ID: {{.EditApp.Subject}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" value="{{.EditApp.Name}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_domain">Domain:</label>
|
||||
<input type="text" name="domain" id="field_domain" value="{{.EditApp.Domain}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public" {{if .EditApp.Public}}checked{{end}}/></label>
|
||||
</div>
|
||||
{{if .IsAdmin}}
|
||||
<div>
|
||||
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso" {{if .EditApp.SSO}}checked{{end}}/></label>
|
||||
</div>
|
||||
{{end}}
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" {{if .EditApp.Active}}checked{{end}}/></label>
|
||||
</div>
|
||||
<button type="submit">Edit</button>
|
||||
</form>
|
||||
<form method="GET" action="/manage/apps">
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<button type="submit">Cancel</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@ -41,42 +41,11 @@
|
||||
<div>New application secret: <span id="app-secret">{{.NewAppSecret}}</span> for {{.NewAppName}}</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Edit}}
|
||||
<h2>Edit Client Application</h2>
|
||||
<form method="POST" action="/manage/apps">
|
||||
<input type="hidden" name="action" value="edit"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<input type="hidden" name="subject" value="{{.Edit.Subject}}"/>
|
||||
<div>
|
||||
<label>ID: {{.Edit.Subject}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" value="{{.Edit.Name}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_domain">Domain:</label>
|
||||
<input type="text" name="domain" id="field_domain" value="{{.Edit.Domain}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public" {{if .Edit.Public}}checked{{end}}/></label>
|
||||
</div>
|
||||
{{if .IsAdmin}}
|
||||
<div>
|
||||
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso" {{if .Edit.SSO}}checked{{end}}/></label>
|
||||
</div>
|
||||
{{end}}
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" {{if .Edit.Active}}checked{{end}}/></label>
|
||||
</div>
|
||||
<button type="submit">Edit</button>
|
||||
</form>
|
||||
<form method="GET" action="/manage/apps">
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<button type="submit">Cancel</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<h2>Manage Client Applications</h2>
|
||||
<form method="GET" action="/manage/apps/create">
|
||||
<button type="submit">New Client Application</button>
|
||||
</form>
|
||||
|
||||
{{if eq (len .Apps) 0}}
|
||||
<div>No client applications found</div>
|
||||
{{else}}
|
||||
@ -121,34 +90,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
|
||||
<h2>Create Client Application</h2>
|
||||
<form method="POST" action="/manage/apps">
|
||||
<input type="hidden" name="action" value="create"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_domain">Domain:</label>
|
||||
<input type="text" name="domain" id="field_domain" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public"/></label>
|
||||
</div>
|
||||
{{if .IsAdmin}}
|
||||
<div>
|
||||
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"/></label>
|
||||
</div>
|
||||
{{end}}
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
||||
checked/></label>
|
||||
</div>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
49
pages/manage-users-create.go.html
Normal file
49
pages/manage-users-create.go.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{.ServiceName}}</title>
|
||||
<link rel="stylesheet" href="/theme/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>{{.ServiceName}}</h1>
|
||||
</header>
|
||||
<main>
|
||||
<form method="GET" action="/">
|
||||
<button type="submit">Home</button>
|
||||
</form>
|
||||
|
||||
<h2>Create User</h2>
|
||||
<form method="POST" action="/manage/users">
|
||||
<input type="hidden" name="action" value="create"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_username">Username:</label>
|
||||
<input type="text" name="username" id="field_username" required/>
|
||||
</div>
|
||||
<div>
|
||||
<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/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_role">Role:</label>
|
||||
<select name="role" id="field_role" required>
|
||||
<option value="member" selected>Member</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
||||
checked/></label>
|
||||
</div>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
51
pages/manage-users-edit.go.html
Normal file
51
pages/manage-users-edit.go.html
Normal file
@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{.ServiceName}}</title>
|
||||
<link rel="stylesheet" href="/theme/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>{{.ServiceName}}</h1>
|
||||
</header>
|
||||
<main>
|
||||
<form method="GET" action="/">
|
||||
<button type="submit">Home</button>
|
||||
</form>
|
||||
|
||||
<h2>Edit User</h2>
|
||||
<form method="POST" action="/manage/users">
|
||||
<input type="hidden" name="action" value="edit"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<input type="hidden" name="subject" value="{{.EditUser.Subject}}"/>
|
||||
<div>
|
||||
<label>ID: {{.EditUser.Subject}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" value="{{.EditUser.Name}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_username">Username:</label>
|
||||
<input type="text" name="username" id="field_username" value="{{.EditUser.Username}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_role">Role:</label>
|
||||
<select name="role" id="field_role" required>
|
||||
<option value="member" {{if (eq .EditUser.Role 0)}}selected{{end}}>Member</option>
|
||||
<option value="admin" {{if (eq .EditUser.Role 1)}}selected{{end}}>Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
||||
checked/></label>
|
||||
</div>
|
||||
<button type="submit">Edit</button>
|
||||
</form>
|
||||
<form method="GET" action="/manage/users">
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<button type="submit">Cancel</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@ -13,42 +13,11 @@
|
||||
<button type="submit">Home</button>
|
||||
</form>
|
||||
|
||||
{{if .Edit}}
|
||||
<h2>Edit User</h2>
|
||||
<form method="POST" action="/manage/users">
|
||||
<input type="hidden" name="action" value="edit"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<input type="hidden" name="subject" value="{{.Edit.Subject}}"/>
|
||||
<div>
|
||||
<label>ID: {{.Edit.Subject}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" value="{{.Edit.Name}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_username">Username:</label>
|
||||
<input type="text" name="username" id="field_username" value="{{.Edit.Username}}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_role">Role:</label>
|
||||
<select name="role" id="field_role" required>
|
||||
<option value="member" {{if (eq .Edit.Role 0)}}selected{{end}}>Member</option>
|
||||
<option value="admin" {{if (eq .Edit.Role 1)}}selected{{end}}>Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
||||
checked/></label>
|
||||
</div>
|
||||
<button type="submit">Edit</button>
|
||||
</form>
|
||||
<form method="GET" action="/manage/users">
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<button type="submit">Cancel</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<h2>Manage Users</h2>
|
||||
<form method="GET" action="/manage/users/create">
|
||||
<button type="submit">Create User</button>
|
||||
</form>
|
||||
|
||||
{{if eq (len .Users) 0}}
|
||||
<div>No users found, this is definitely a bug.</div>
|
||||
{{else}}
|
||||
@ -118,39 +87,6 @@
|
||||
<button type="submit">{{if .EmailShow}}Hide Email Addresses{{else}}Show email addresses{{end}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
<h2>Create User</h2>
|
||||
<form method="POST" action="/manage/users">
|
||||
<input type="hidden" name="action" value="create"/>
|
||||
<input type="hidden" name="offset" value="{{.Offset}}"/>
|
||||
<div>
|
||||
<label for="field_name">Name:</label>
|
||||
<input type="text" name="name" id="field_name" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_username">Username:</label>
|
||||
<input type="text" name="username" id="field_username" required/>
|
||||
</div>
|
||||
<div>
|
||||
<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/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_role">Role:</label>
|
||||
<select name="role" id="field_role" required>
|
||||
<option value="member" selected>Member</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
||||
checked/></label>
|
||||
</div>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,34 +1,36 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"github.com/1f349/overlapfs"
|
||||
"github.com/1f349/tulip/logger"
|
||||
"github.com/1f349/tulip/utils"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed *.go.html
|
||||
//go:embed *.go.html assets/*.css
|
||||
wwwPages embed.FS
|
||||
wwwTemplates *template.Template
|
||||
loadOnce sync.Once
|
||||
loadOnce utils.Once[error]
|
||||
cssAssetMap = make(map[string][]byte)
|
||||
)
|
||||
|
||||
func LoadPages(wd string) (err error) {
|
||||
loadOnce.Do(func() {
|
||||
return loadOnce.Do(func() error {
|
||||
var o fs.FS = wwwPages
|
||||
if wd != "" {
|
||||
wwwDir := filepath.Join(wd, "www")
|
||||
err = os.Mkdir(wwwDir, os.ModePerm)
|
||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||
return
|
||||
return err
|
||||
}
|
||||
wdFs := os.DirFS(wwwDir)
|
||||
o = overlapfs.OverlapFS{A: wwwPages, B: wdFs}
|
||||
@ -36,8 +38,19 @@ func LoadPages(wd string) (err error) {
|
||||
wwwTemplates, err = template.New("pages").Funcs(template.FuncMap{
|
||||
"emailHide": EmailHide,
|
||||
}).ParseFS(o, "*.go.html")
|
||||
})
|
||||
|
||||
glob, err := fs.Glob(o, "assets/*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range glob {
|
||||
cssAssetMap[i], err = fs.ReadFile(o, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func RenderPageTemplate(wr io.Writer, name string, data any) {
|
||||
@ -48,6 +61,14 @@ func RenderPageTemplate(wr io.Writer, name string, data any) {
|
||||
}
|
||||
}
|
||||
|
||||
func RenderCss(name string) io.ReadSeeker {
|
||||
b, ok := cssAssetMap[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return bytes.NewReader(b)
|
||||
}
|
||||
|
||||
func EmailHide(a string) string {
|
||||
b := []byte(a)
|
||||
for i := range b {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
@ -45,9 +46,10 @@ func TestRequireAuthentication(t *testing.T) {
|
||||
|
||||
func TestOptionalAuthentication(t *testing.T) {
|
||||
h := &HttpServer{}
|
||||
rec := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "https://example.com/hello", nil)
|
||||
assert.NoError(t, err)
|
||||
auth, err := h.internalAuthenticationHandler(nil, req)
|
||||
auth, err := h.internalAuthenticationHandler(rec, req)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, auth.IsGuest())
|
||||
auth.Subject = "567"
|
||||
|
@ -52,20 +52,41 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
|
||||
if q.Has("edit") {
|
||||
for _, i := range appList {
|
||||
if i.Subject == q.Get("edit") {
|
||||
m["Edit"] = i
|
||||
goto validEdit
|
||||
m["EditApp"] = i
|
||||
rw.Header().Set("Content-Type", "text/html")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
pages.RenderPageTemplate(rw, "manage-apps-edit", m)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Error(rw, "400 Bad Request: Invalid client app to edit", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
validEdit:
|
||||
rw.Header().Set("Content-Type", "text/html")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
pages.RenderPageTemplate(rw, "manage-apps", m)
|
||||
}
|
||||
|
||||
func (h *HttpServer) ManageAppsCreateGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
||||
var roles types.UserRole
|
||||
if h.DbTx(rw, func(tx *database.Queries) (err error) {
|
||||
roles, err = tx.GetUserRole(req.Context(), auth.Subject)
|
||||
return
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
||||
m := map[string]any{
|
||||
"ServiceName": h.conf.ServiceName,
|
||||
"IsAdmin": roles == types.RoleAdmin,
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "text/html")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
pages.RenderPageTemplate(rw, "manage-apps-create", m)
|
||||
}
|
||||
|
||||
func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
||||
err := req.ParseForm()
|
||||
if err != nil {
|
||||
|
@ -57,20 +57,42 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _
|
||||
if q.Has("edit") {
|
||||
for _, i := range userList {
|
||||
if i.Subject == q.Get("edit") {
|
||||
m["Edit"] = i
|
||||
goto validEdit
|
||||
m["EditUser"] = i
|
||||
rw.Header().Set("Content-Type", "text/html")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
pages.RenderPageTemplate(rw, "manage-users-edit", m)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Error(rw, "400 Bad Request: Invalid user to edit", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
validEdit:
|
||||
rw.Header().Set("Content-Type", "text/html")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
pages.RenderPageTemplate(rw, "manage-users", m)
|
||||
}
|
||||
|
||||
func (h *HttpServer) ManageUsersCreateGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
||||
var roles types.UserRole
|
||||
if h.DbTx(rw, func(tx *database.Queries) (err error) {
|
||||
roles, err = tx.GetUserRole(req.Context(), auth.Subject)
|
||||
return
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
||||
m := map[string]any{
|
||||
"ServiceName": h.conf.ServiceName,
|
||||
"IsAdmin": roles == types.RoleAdmin,
|
||||
"Namespace": h.conf.Namespace,
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "text/html")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
pages.RenderPageTemplate(rw, "manage-users-create", m)
|
||||
}
|
||||
|
||||
func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
||||
err := req.ParseForm()
|
||||
if err != nil {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
@ -12,8 +11,8 @@ import (
|
||||
"github.com/1f349/tulip/database"
|
||||
"github.com/1f349/tulip/logger"
|
||||
"github.com/1f349/tulip/openid"
|
||||
"github.com/1f349/tulip/pages"
|
||||
scope2 "github.com/1f349/tulip/scope"
|
||||
"github.com/1f349/tulip/theme"
|
||||
"github.com/go-oauth2/oauth2/v4/errors"
|
||||
"github.com/go-oauth2/oauth2/v4/manage"
|
||||
"github.com/go-oauth2/oauth2/v4/server"
|
||||
@ -21,6 +20,7 @@ import (
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -52,6 +52,7 @@ type mailLinkKey struct {
|
||||
|
||||
func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *http.Server {
|
||||
r := httprouter.New()
|
||||
contentCache := time.Now()
|
||||
|
||||
// remove last slash from baseUrl
|
||||
{
|
||||
@ -145,8 +146,14 @@ func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *htt
|
||||
}))
|
||||
|
||||
// theme styles
|
||||
r.GET("/theme/style.css", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||
http.ServeContent(rw, req, "style.css", time.Now(), bytes.NewReader(theme.DefaultThemeCss))
|
||||
r.GET("/assets/*filepath", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||
name := params.ByName("filepath")
|
||||
if strings.Contains(name, "..") {
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
out := pages.RenderCss(path.Join("assets", name))
|
||||
http.ServeContent(rw, req, path.Base(name), contentCache, out)
|
||||
})
|
||||
|
||||
// login steps
|
||||
@ -168,8 +175,10 @@ func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *htt
|
||||
|
||||
// management pages
|
||||
r.GET("/manage/apps", hs.RequireAuthentication(hs.ManageAppsGet))
|
||||
r.GET("/manage/apps/create", hs.RequireAuthentication(hs.ManageAppsCreateGet))
|
||||
r.POST("/manage/apps", hs.RequireAuthentication(hs.ManageAppsPost))
|
||||
r.GET("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersGet))
|
||||
r.GET("/manage/users/create", hs.RequireAuthentication(hs.ManageUsersCreateGet))
|
||||
r.POST("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersPost))
|
||||
|
||||
// oauth pages
|
||||
|
@ -1,6 +0,0 @@
|
||||
package theme
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed style.css
|
||||
var DefaultThemeCss []byte
|
15
utils/once.go
Normal file
15
utils/once.go
Normal file
@ -0,0 +1,15 @@
|
||||
package utils
|
||||
|
||||
import "sync"
|
||||
|
||||
type Once[T any] struct {
|
||||
once sync.Once
|
||||
value T
|
||||
}
|
||||
|
||||
func (o *Once[T]) Do(f func() T) T {
|
||||
o.once.Do(func() {
|
||||
o.value = f()
|
||||
})
|
||||
return o.value
|
||||
}
|
Loading…
Reference in New Issue
Block a user