first working version
This commit is contained in:
parent
261ebed165
commit
c9dfecc519
21 changed files with 296 additions and 178 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
|||
tmp
|
||||
run.sh
|
||||
TODO.txt
|
||||
consent.json
|
||||
|
|
|
|||
|
|
@ -31,6 +31,6 @@ type Config struct {
|
|||
HydraAdminURL string `json:"hydra_admin_url"`
|
||||
Issuer string `json:"issuer"`
|
||||
Version string `json:"-"`
|
||||
Admin bool `json:"admin,omitempty"`
|
||||
AdminDisabled bool `json:"admin_disabled,omitempty"`
|
||||
SecureCookie bool `json:"secure_cookie,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ type User struct {
|
|||
Created string
|
||||
Secret string
|
||||
Admin bool
|
||||
Disabled bool
|
||||
Locked int
|
||||
}
|
||||
|
||||
|
|
|
|||
10
pkg/db/db.go
10
pkg/db/db.go
|
|
@ -21,12 +21,11 @@ const (
|
|||
|
||||
type DB struct {
|
||||
log logr.Logger
|
||||
admin bool
|
||||
store store.Store
|
||||
}
|
||||
|
||||
func NewPlain(log logr.Logger, cfg *core.Config) (db *DB, err error) {
|
||||
db = &DB{log: log, admin: cfg.Admin}
|
||||
db = &DB{log: log}
|
||||
dbFile := filepath.Join(cfg.DataDir, storeFile)
|
||||
//err = godrop.Unveil(dbFile, "rwc")
|
||||
//if err != nil {
|
||||
|
|
@ -42,7 +41,12 @@ func New(log logr.Logger, cfg *core.Config) (db *DB, err error) {
|
|||
return
|
||||
}
|
||||
if db.UserExists(defUser) != nil {
|
||||
err = db.CreateUser(defUser, defEmail, defPassword, true)
|
||||
err = db.CreateUser(defUser, defEmail, defPassword, true, cfg.AdminDisabled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = db.AdminUpdateUser(defUser, "", true, cfg.AdminDisabled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (db *DB) UserExists(username string) error {
|
|||
return db.store.Get(userPrefix+username, nil)
|
||||
}
|
||||
|
||||
func (db *DB) CreateUser(username, email, password string, admin bool) error {
|
||||
func (db *DB) CreateUser(username, email, password string, admin, disabled bool) error {
|
||||
username = userRe.ReplaceAllString(username, "")
|
||||
if err := db.UserExists(username); err == nil {
|
||||
return errUserExists
|
||||
|
|
@ -77,18 +77,19 @@ func (db *DB) CreateUser(username, email, password string, admin bool) error {
|
|||
Password: string(hash),
|
||||
Created: strconv.Itoa(int(jwt.Now())),
|
||||
Admin: admin,
|
||||
Disabled: disabled,
|
||||
})
|
||||
}
|
||||
|
||||
func (db *DB) Login(username, password string) (u *core.User, err error) {
|
||||
if username == defUser && !db.admin {
|
||||
err = errors.New("db: user disabled")
|
||||
return
|
||||
}
|
||||
u, err = db.GetUser(username)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if u.Disabled {
|
||||
err = errors.New("db: user disabled")
|
||||
return
|
||||
}
|
||||
if u.Locked == 3 {
|
||||
err = errUserLocked
|
||||
return
|
||||
|
|
@ -185,7 +186,7 @@ func (db *DB) ResetAdmin() error {
|
|||
return db.UpdateUserPassword(defUser, defPassword)
|
||||
}
|
||||
|
||||
func (db *DB) AdminUpdateUser(username, password string, admin bool) error {
|
||||
func (db *DB) AdminUpdateUser(username, password string, admin, disabled bool) error {
|
||||
if username == defUser && !admin {
|
||||
return errors.New("user '" + defUser + "' cannot lose admin privileges")
|
||||
}
|
||||
|
|
@ -194,6 +195,7 @@ func (db *DB) AdminUpdateUser(username, password string, admin bool) error {
|
|||
return err
|
||||
}
|
||||
u.Admin = admin
|
||||
u.Disabled = disabled
|
||||
if password != "" {
|
||||
hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
|
@ -87,7 +86,7 @@ type loginData struct {
|
|||
|
||||
type consentData struct {
|
||||
ConsentChallenge string
|
||||
Info *consentInfo
|
||||
Info *client.ConsentRequest
|
||||
}
|
||||
|
||||
func (c *Context) Exec() {
|
||||
|
|
@ -302,155 +301,117 @@ func (c *Context) Admin() bool {
|
|||
return c.Token.Claims.GetBool(adminClaim)
|
||||
}
|
||||
|
||||
type respBody struct {
|
||||
URL string `json:"redirect_to,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Context) ApproveLogin(user, challenge string) (rdr string, err error) {
|
||||
req := c.Srv.API.AdminApi.AcceptLoginRequest(context.Background())
|
||||
req.AcceptLoginRequest(*client.NewAcceptLoginRequest(user))
|
||||
req.LoginChallenge(challenge)
|
||||
_, resp, err := req.Execute()
|
||||
cr, resp, err := c.Srv.API.AdminApi.AcceptLoginRequest(context.Background()).
|
||||
AcceptLoginRequest(*client.NewAcceptLoginRequest(user)).
|
||||
LoginChallenge(challenge).
|
||||
Execute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
rb := new(respBody)
|
||||
err = json.NewDecoder(resp.Body).Decode(rb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if rb.Error != "" {
|
||||
err = errors.New(rb.Error)
|
||||
return
|
||||
}
|
||||
rdr = rb.URL
|
||||
rdr = cr.RedirectTo
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Context) ApproveConsent(challenge string) (rdr string, err error) {
|
||||
req := c.Srv.API.AdminApi.AcceptConsentRequest(context.Background()).
|
||||
AcceptConsentRequest(*client.NewAcceptConsentRequest()).
|
||||
ConsentChallenge(challenge)
|
||||
_, resp, err := req.Execute()
|
||||
ci, err := c.Consent(challenge)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
accept := client.NewAcceptConsentRequest()
|
||||
accept.GrantScope = ci.RequestedScope
|
||||
accept.GrantAccessTokenAudience = ci.RequestedAccessTokenAudience
|
||||
accept.Remember = new(bool)
|
||||
*accept.Remember = true
|
||||
cr, resp, err := c.Srv.API.AdminApi.AcceptConsentRequest(context.Background()).
|
||||
AcceptConsentRequest(*accept).
|
||||
ConsentChallenge(challenge).
|
||||
Execute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
rb := new(respBody)
|
||||
err = json.NewDecoder(resp.Body).Decode(rb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if rb.Error != "" {
|
||||
err = errors.New(rb.Error)
|
||||
return
|
||||
}
|
||||
rdr = rb.URL
|
||||
rdr = cr.RedirectTo
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Context) RejectConsent(challenge string) (rdr string, err error) {
|
||||
req := c.Srv.API.AdminApi.RejectConsentRequest(context.Background()).
|
||||
cr, resp, err := c.Srv.API.AdminApi.RejectConsentRequest(context.Background()).
|
||||
RejectRequest(*client.NewRejectRequest()).
|
||||
ConsentChallenge(challenge)
|
||||
_, resp, err := req.Execute()
|
||||
ConsentChallenge(challenge).
|
||||
Execute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
rb := new(respBody)
|
||||
err = json.NewDecoder(resp.Body).Decode(rb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if rb.Error != "" {
|
||||
err = errors.New(rb.Error)
|
||||
return
|
||||
}
|
||||
rdr = rb.URL
|
||||
rdr = cr.RedirectTo
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Context) Consent(challenge string) (ci *consentInfo, err error) {
|
||||
req := c.Srv.API.AdminApi.GetConsentRequest(context.Background()).
|
||||
ConsentChallenge(challenge)
|
||||
_, resp, err := req.Execute()
|
||||
func (c *Context) Consent(challenge string) (cr *client.ConsentRequest, err error) {
|
||||
cr, resp, err := c.Srv.API.AdminApi.GetConsentRequest(context.Background()).
|
||||
ConsentChallenge(challenge).
|
||||
Execute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
ci = new(consentInfo)
|
||||
err = json.NewDecoder(resp.Body).Decode(ci)
|
||||
return
|
||||
}
|
||||
|
||||
type sess struct {
|
||||
ID string
|
||||
Time string
|
||||
}
|
||||
|
||||
func (c *Context) Sessions(user string) (m map[string]sess, err error) {
|
||||
ss, resp, err := c.Srv.API.AdminApi.ListSubjectConsentSessions(context.Background()).
|
||||
Subject(user).
|
||||
Execute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ci.Error != "" {
|
||||
err = errors.New(ci.Error)
|
||||
defer resp.Body.Close()
|
||||
m = make(map[string]sess)
|
||||
for _, s := range ss {
|
||||
name := s.ConsentRequest.Client.ClientName
|
||||
if name == nil {
|
||||
continue
|
||||
}
|
||||
n := *name
|
||||
cid := s.ConsentRequest.Client.ClientId
|
||||
if cid == nil {
|
||||
continue
|
||||
}
|
||||
id := *cid
|
||||
if _, ok := m[n]; !ok {
|
||||
m[n] = sess{
|
||||
id,
|
||||
s.HandledAt.Format(timeFmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type consentInfo struct {
|
||||
Acr string `json:"acr"`
|
||||
Amr []string `json:"amr"`
|
||||
Challenge string `json:"challenge"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Client struct {
|
||||
AllowedCorsOrigins []string `json:"allowed_cors_origins"`
|
||||
Audience []string `json:"audience"`
|
||||
BackchannelLogoutSessionRequired bool `json:"backchannel_logout_session_required"`
|
||||
BackchannelLogoutURI string `json:"backchannel_logout_uri"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientName string `json:"client_name"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
ClientSecretExpiresAt int `json:"client_secret_expires_at"`
|
||||
ClientURI string `json:"client_uri"`
|
||||
Contacts []string `json:"contacts"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
FrontchannelLogoutSessionRequired bool `json:"frontchannel_logout_session_required"`
|
||||
FrontchannelLogoutURI string `json:"frontchannel_logout_uri"`
|
||||
GrantTypes []string `json:"grant_types"`
|
||||
Jwks struct {
|
||||
} `json:"jwks"`
|
||||
JwksURI string `json:"jwks_uri"`
|
||||
LogoURI string `json:"logo_uri"`
|
||||
Metadata struct {
|
||||
} `json:"metadata"`
|
||||
Owner string `json:"owner"`
|
||||
PolicyURI string `json:"policy_uri"`
|
||||
PostLogoutRedirectUris []string `json:"post_logout_redirect_uris"`
|
||||
RedirectUris []string `json:"redirect_uris"`
|
||||
RegistrationAccessToken string `json:"registration_access_token"`
|
||||
RegistrationClientURI string `json:"registration_client_uri"`
|
||||
RequestObjectSigningAlg string `json:"request_object_signing_alg"`
|
||||
RequestUris []string `json:"request_uris"`
|
||||
ResponseTypes []string `json:"response_types"`
|
||||
Scope string `json:"scope"`
|
||||
SectorIdentifierURI string `json:"sector_identifier_uri"`
|
||||
SubjectType string `json:"subject_type"`
|
||||
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"`
|
||||
TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg"`
|
||||
TosURI string `json:"tos_uri"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UserinfoSignedResponseAlg string `json:"userinfo_signed_response_alg"`
|
||||
} `json:"client"`
|
||||
LoginChallenge string `json:"login_challenge"`
|
||||
LoginSessionID string `json:"login_session_id"`
|
||||
OidcContext struct {
|
||||
AcrValues []string `json:"acr_values"`
|
||||
Display string `json:"display"`
|
||||
IDTokenHintClaims struct {
|
||||
} `json:"id_token_hint_claims"`
|
||||
LoginHint string `json:"login_hint"`
|
||||
UILocales []string `json:"ui_locales"`
|
||||
} `json:"oidc_context"`
|
||||
RequestURL string `json:"request_url"`
|
||||
RequestedAccessTokenAudience []string `json:"requested_access_token_audience"`
|
||||
RequestedScope []string `json:"requested_scope"`
|
||||
Skip bool `json:"skip"`
|
||||
Subject string `json:"subject"`
|
||||
func (c *Context) RevokeSession(user, client string, all bool) (err error) {
|
||||
if client == "" && !all {
|
||||
return
|
||||
}
|
||||
req := c.Srv.API.AdminApi.RevokeConsentSessions(context.Background()).
|
||||
Subject(user)
|
||||
if all {
|
||||
req = req.All(all)
|
||||
} else {
|
||||
req = req.Client(client)
|
||||
}
|
||||
resp, err := req.Execute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return
|
||||
}
|
||||
|
||||
type ctxHandler func(*Context)
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ func indexHandler(ctx *Context) {
|
|||
ctx.Redirect(login, http.StatusFound)
|
||||
}
|
||||
|
||||
func profileHandler(ctx *Context) {
|
||||
ctx.Redirect(uroot+"edit/"+ctx.User(), http.StatusFound)
|
||||
func sessionsHandler(ctx *Context) {
|
||||
ctx.Redirect(uroot+"sessions/"+ctx.User(), http.StatusFound)
|
||||
}
|
||||
|
||||
func loginHandler(ctx *Context) {
|
||||
|
|
@ -123,7 +123,7 @@ func loginHandler(ctx *Context) {
|
|||
BodyTitle: "Login",
|
||||
}
|
||||
if ctx.LoggedOn() {
|
||||
rdr := profile
|
||||
rdr := sessions
|
||||
if data.LoginChallenge != "" {
|
||||
rdr, err = ctx.ApproveLogin(ctx.User(), data.LoginChallenge)
|
||||
if err != nil {
|
||||
|
|
@ -172,7 +172,7 @@ func loginHandler(ctx *Context) {
|
|||
}
|
||||
log.Info("user logged in", "user", data.User)
|
||||
ctx.Login(u, remember)
|
||||
rdr := profile
|
||||
rdr := sessions
|
||||
if data.LoginChallenge != "" {
|
||||
rdr, err = ctx.ApproveLogin(data.User, data.LoginChallenge)
|
||||
if err != nil {
|
||||
|
|
@ -187,7 +187,7 @@ func loginHandler(ctx *Context) {
|
|||
|
||||
func loginTotpHandler(ctx *Context) {
|
||||
if ctx.LoggedOn() {
|
||||
ctx.Redirect(profile, http.StatusFound)
|
||||
ctx.Redirect(sessions, http.StatusFound)
|
||||
return
|
||||
}
|
||||
ctx.Template("loginTotpHandler")
|
||||
|
|
@ -222,7 +222,7 @@ func loginTotpHandler(ctx *Context) {
|
|||
}
|
||||
log.Info("totp successful")
|
||||
ctx.Login(u, ctx.Remember())
|
||||
rdr := root
|
||||
rdr := sessions
|
||||
if data.LoginChallenge != "" {
|
||||
rdr, err = ctx.ApproveLogin(user, data.LoginChallenge)
|
||||
if err != nil {
|
||||
|
|
@ -239,21 +239,30 @@ func loginTotpHandler(ctx *Context) {
|
|||
func consentHandler(ctx *Context) {
|
||||
ctx.Template("consentHandler")
|
||||
ctx.Data = webData{
|
||||
Title: "Consent",
|
||||
BodyTitle: "Consent",
|
||||
Title: "Authorize",
|
||||
BodyTitle: "Authorize",
|
||||
}
|
||||
var data consentData
|
||||
switch ctx.Method() {
|
||||
case "GET":
|
||||
data.ConsentChallenge = ctx.Query("consent_challenge")
|
||||
ctx.Data.Data = data
|
||||
ci, err := ctx.Consent(data.ConsentChallenge)
|
||||
cr, err := ctx.Consent(data.ConsentChallenge)
|
||||
if err != nil {
|
||||
ctx.log.Error(err, "error getting consent info")
|
||||
ctx.Error("error getting consent info")
|
||||
return
|
||||
}
|
||||
data.Info = ci
|
||||
if cr.GetSkip() {
|
||||
rdr, err := ctx.ApproveConsent(data.ConsentChallenge)
|
||||
if err != nil {
|
||||
ctx.log.Error(err, "error approving consent")
|
||||
ctx.Error("error approving consent")
|
||||
return
|
||||
}
|
||||
ctx.Redirect(rdr, http.StatusFound)
|
||||
}
|
||||
data.Info = cr
|
||||
ctx.Data.Data = data
|
||||
ctx.Exec()
|
||||
case "POST":
|
||||
|
|
@ -321,11 +330,8 @@ func userNewHandler(ctx *Context) {
|
|||
email := ctx.Form("email")
|
||||
password := ctx.Form("password")
|
||||
repeat := ctx.Form("repeat")
|
||||
admin := ctx.Form("admin")
|
||||
adm := false
|
||||
if admin == "0" {
|
||||
adm = true
|
||||
}
|
||||
admin := ctx.Form("admin") == "0"
|
||||
disabled := ctx.Form("disabled") == "0"
|
||||
|
||||
if user == "" || email == "" || password == "" {
|
||||
ctx.Error("empty user, email or password")
|
||||
|
|
@ -335,11 +341,11 @@ func userNewHandler(ctx *Context) {
|
|||
ctx.Error("passwords do not match")
|
||||
return
|
||||
}
|
||||
if err := ctx.Srv.DB.CreateUser(user, email, password, adm); err != nil {
|
||||
if err := ctx.Srv.DB.CreateUser(user, email, password, admin, disabled); err != nil {
|
||||
ctx.Error(err)
|
||||
return
|
||||
}
|
||||
ctx.log.Info("user created", "user", user, "actor", ctx.User())
|
||||
ctx.log.Info("user created", "user", user, "actor", ctx.User(), "admin", admin, "disabled", disabled)
|
||||
ctx.Redirect(users, http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
|
@ -364,25 +370,27 @@ func userEditHandler(ctx *Context) {
|
|||
return
|
||||
}
|
||||
user := ctx.Var("user")
|
||||
log := ctx.log.WithValues("user", user, "actor", ctx.User())
|
||||
self := ctx.User()
|
||||
log := ctx.log.WithValues("user", user, "actor", self)
|
||||
password := ctx.Form("password")
|
||||
repeat := ctx.Form("repeat")
|
||||
admin := ctx.Form("admin")
|
||||
adm := false
|
||||
if admin == "0" {
|
||||
adm = true
|
||||
}
|
||||
admin := ctx.Form("admin") == "0"
|
||||
disabled := ctx.Form("disabled") == "0"
|
||||
|
||||
if password != repeat {
|
||||
ctx.Error("passwords do not match")
|
||||
return
|
||||
}
|
||||
if ctx.Admin() {
|
||||
if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil {
|
||||
if err := ctx.Srv.DB.AdminUpdateUser(user, password, admin, disabled); err != nil {
|
||||
ctx.Error(err)
|
||||
return
|
||||
}
|
||||
log.Info("user updated", "admin", adm)
|
||||
log.Info("user updated", "admin", admin, "disabled", disabled)
|
||||
if user == self {
|
||||
logoutHandler(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(users, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
|
@ -461,6 +469,40 @@ func userTotpHandler(ctx *Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func userSessionsHandler(ctx *Context) {
|
||||
ctx.Template("userSessionsHandler")
|
||||
ctx.Data = webData{
|
||||
Title: "Sessions",
|
||||
BodyTitle: "Sessions",
|
||||
}
|
||||
user := ctx.Var("user")
|
||||
log := ctx.log.WithValues("user", user)
|
||||
switch ctx.Method() {
|
||||
case "GET":
|
||||
s, err := ctx.Sessions(user)
|
||||
if err != nil {
|
||||
log.Error(err, "error getting sessions")
|
||||
ctx.Error("error getting sessions")
|
||||
return
|
||||
}
|
||||
ctx.Data.Data = s
|
||||
ctx.Exec()
|
||||
case "POST":
|
||||
if !ctx.CheckXsrf() {
|
||||
return
|
||||
}
|
||||
client := ctx.Form("client")
|
||||
all := ctx.Form("all") == "all"
|
||||
err := ctx.RevokeSession(user, client, all)
|
||||
if err != nil {
|
||||
log.Error(err, "error revoking session", "client", client, "all", all)
|
||||
ctx.Error("error revoking session")
|
||||
return
|
||||
}
|
||||
sessionsHandler(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func userUnlockHandler(ctx *Context) {
|
||||
user := ctx.Var("user")
|
||||
log := ctx.log.WithValues("user", user, "actor", ctx.User())
|
||||
|
|
@ -502,7 +544,7 @@ func userDelHandler(ctx *Context) {
|
|||
log.Info("delete successful")
|
||||
}
|
||||
if user == self {
|
||||
ctx.Redirect(logout, http.StatusFound)
|
||||
logoutHandler(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(users, http.StatusFound)
|
||||
|
|
|
|||
|
|
@ -10,4 +10,6 @@ const (
|
|||
day = time.Hour * 24
|
||||
min = time.Second * 30
|
||||
max = day * 90
|
||||
|
||||
timeFmt = "2006-01-02 15:04:05"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,20 +9,21 @@ type route struct {
|
|||
}
|
||||
|
||||
const (
|
||||
root = "/"
|
||||
login = root + "login"
|
||||
totp = root + "totp"
|
||||
profile = root + "profile"
|
||||
consent = root + "consent"
|
||||
users = root + "users"
|
||||
logout = root + "logout"
|
||||
uroot = root + "user/"
|
||||
root = "/"
|
||||
login = root + "login"
|
||||
totp = root + "totp"
|
||||
sessions = root + "sessions"
|
||||
consent = root + "consent"
|
||||
users = root + "users"
|
||||
logout = root + "logout"
|
||||
uroot = root + "user/"
|
||||
|
||||
userNew = uroot + "new"
|
||||
userEdit = uroot + "edit/{user:[a-zA-Z0-9]+$}"
|
||||
userDel = uroot + "del/{user:[a-zA-Z0-9]+$}"
|
||||
userTotp = uroot + "totp/{user:[a-zA-Z0-9]+$}"
|
||||
userUnlock = uroot + "unlock/{user:[a-zA-Z0-9]+$}"
|
||||
userNew = uroot + "new"
|
||||
userEdit = uroot + "edit/{user:[a-zA-Z0-9]+$}"
|
||||
userDel = uroot + "del/{user:[a-zA-Z0-9]+$}"
|
||||
userTotp = uroot + "totp/{user:[a-zA-Z0-9]+$}"
|
||||
userSessions = uroot + "sessions/{user:[a-zA-Z0-9]+$}"
|
||||
userUnlock = uroot + "unlock/{user:[a-zA-Z0-9]+$}"
|
||||
)
|
||||
|
||||
var routes = []route{
|
||||
|
|
@ -48,15 +49,15 @@ var routes = []route{
|
|||
{
|
||||
consent,
|
||||
jwtHandler(
|
||||
userAuthHandler(
|
||||
authHandler(
|
||||
consentHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
profile,
|
||||
sessions,
|
||||
jwtHandler(
|
||||
userAuthHandler(
|
||||
profileHandler)),
|
||||
authHandler(
|
||||
sessionsHandler)),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
|
|
@ -100,6 +101,13 @@ var routes = []route{
|
|||
userTotpHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
userSessions,
|
||||
jwtHandler(
|
||||
userAuthHandler(
|
||||
userSessionsHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
userUnlock,
|
||||
jwtHandler(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ func (s *Server) loadTemplates() (err error) {
|
|||
|
||||
// pages
|
||||
s.ts.Parse(tfs, "notFoundHandler", "index.html", "menu.html", "notFound.html")
|
||||
s.ts.Parse(tfs, "forbiddenHandler", "index.html", "menu.html", "forbidden.html")
|
||||
s.ts.Parse(tfs, "loginHandler", "index.html", "menu.html", "login.html")
|
||||
s.ts.Parse(tfs, "loginTotpHandler", "index.html", "menu.html", "loginTotp.html")
|
||||
s.ts.Parse(tfs, "consentHandler", "index.html", "menu.html", "consent.html")
|
||||
|
|
@ -51,6 +52,7 @@ func (s *Server) loadTemplates() (err error) {
|
|||
s.ts.Parse(tfs, "userNewHandler", "index.html", "menu.html", "userNew.html")
|
||||
s.ts.Parse(tfs, "userEditHandler", "index.html", "menu.html", "userEdit.html")
|
||||
s.ts.Parse(tfs, "userDelHandler", "index.html", "menu.html", "userDel.html")
|
||||
s.ts.Parse(tfs, "userSessionsHandler", "index.html", "menu.html", "userSessions.html")
|
||||
// totp
|
||||
s.ts.Parse(tfs, "userTotpHandler", "index.html", "menu.html", "userTotp.html")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
{{define "body"}}
|
||||
{{if .Data}}
|
||||
<div class="row">
|
||||
<div class="col-xs-4 offset-4">
|
||||
<div class="col-xs-4 offset-3">
|
||||
<div class="card border-primary mx-auto">
|
||||
<div class="card-header">
|
||||
<strong>{{.BodyTitle}}</strong>
|
||||
<strong>{{.BodyTitle}} - {{.Data.Info.Client.ClientName}}</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre>
|
||||
{{.Data.Info}}
|
||||
</pre>
|
||||
The client <code>{{.Data.Info.Client.ClientName}}</code> is requesting access to your data.<br>
|
||||
Requested Scopes:
|
||||
<ul>
|
||||
{{range $item := .Data.Info.RequestedScope}}
|
||||
<li>{{$item}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
<form class="form-horizontal" action="/consent" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<input type="hidden" name="consent_challenge" value="{{.Data.ConsentChallenge}}">
|
||||
|
|
|
|||
17
pkg/server/templates/forbidden.html
Normal file
17
pkg/server/templates/forbidden.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 offset-4">
|
||||
<div class="card border-danger mx-auto">
|
||||
<div class="card-header">
|
||||
<strong>403 - Forbidden</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5>Access to this page is restricted</h5>
|
||||
<a href="/" class="btn btn-sm btn-primary">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -9,7 +9,9 @@
|
|||
<div class="card-body">
|
||||
<form class="form-horizontal" action="/login" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
{{if .Data}}
|
||||
<input type="hidden" name="login_challenge" value="{{.Data.LoginChallenge}}">
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label class="col-form-label" for="user">Username</label>
|
||||
<input class="form-control input-sm" type="text" id="user" name="user" value="{{.Data.User}}" autofocus required>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
<div class="card-body">
|
||||
<form class="form-horizontal" action="/totp" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
{{if .Data}}
|
||||
<input type="hidden" name="login_challenge" value="{{.Data.LoginChallenge}}">
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label class="col-form-label" for="pin">PIN</label>
|
||||
<input class="form-control input-sm" type="text" id="pin" name="pin" autocomplete="off" autofocus required>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
{{define "menu"}}
|
||||
{{if .Admin}}
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item"><a class="nav-link" href="/users">Users</a></li>
|
||||
</ul>
|
||||
{{end}}
|
||||
<ul class="nav navbar-nav ml-auto">
|
||||
{{if .Login}}
|
||||
{{if .Admin}}
|
||||
<li class="nav-item"><a class="nav-link" href="/users">Users</a></li>
|
||||
{{else}}
|
||||
<li class="nav-item"><a class="nav-link" href="/user/sessions/{{.User}}">Sessions</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/user/edit/{{.User}}">Profile</a></li>
|
||||
{{end}}
|
||||
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
|
||||
{{else}}
|
||||
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<strong>Delete {{.Data.Username}}?</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>You are about to delete user <a href="/{{.Data.Username}}">{{.Data.Username}}</a>.</p>
|
||||
<p>You are about to delete user <a href="/user/edit/{{.Data.Username}}">{{.Data.Username}}</a>.</p>
|
||||
<form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@
|
|||
<label class="custom-control-label" for="admin">Admin Privileges</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
{{if .Data.Disabled}}
|
||||
<input type="checkbox" class="custom-control-input" id="disabled" name="disabled" value="0" checked>
|
||||
{{else}}
|
||||
<input type="checkbox" class="custom-control-input" id="disabled" name="disabled" value="0">
|
||||
{{end}}
|
||||
<label class="custom-control-label" for="disabled">Disabled</label>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<button class="btn btn-sm btn-primary" type="submit">Update</button>
|
||||
{{if .Admin}}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@
|
|||
<label class="custom-control-label" for="admin">Admin Privileges</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="disabled" name="disabled" value="0">
|
||||
<label class="custom-control-label" for="disabled">Disabled</label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary" type="submit">Create</button>
|
||||
<a href="/users" class="btn btn-sm btn-primary">Back</a>
|
||||
</form>
|
||||
|
|
|
|||
50
pkg/server/templates/userSessions.html
Normal file
50
pkg/server/templates/userSessions.html
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
{{$user := .User}}
|
||||
{{$token := .Token}}
|
||||
{{if .Data}}
|
||||
<div class="row mb-3">
|
||||
<div class="col-xs-5">
|
||||
<form class="form-horizontal" action="/user/sessions/{{$user}}" method="post">
|
||||
<input type="hidden" name="token" value="{{$token}}">
|
||||
<input type="hidden" name="all" value="all">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Revoke All</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{if .Data}}
|
||||
<table class="table table-hover">
|
||||
<thead><tr>
|
||||
<th scope="col">Client Name</th>
|
||||
<th scope="col">Created</th>
|
||||
<th scope="col">Options</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{{range $name, $s := .Data}}
|
||||
<tr>
|
||||
<th scope="row">{{$name}}</th>
|
||||
<td>{{$s.Time}}</td>
|
||||
<td>
|
||||
<form class="form-horizontal" action="/user/sessions/{{$user}}" method="post">
|
||||
<input type="hidden" name="token" value="{{$token}}">
|
||||
<input type="hidden" name="client" value="{{$s.ID}}">
|
||||
<button class="btn btn-sm btn-warning" type="submit">Revoke</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
No active sessions found.
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -31,13 +31,15 @@
|
|||
<td>{{$item.Username}}</td>
|
||||
<td>{{$item.Email}}</td>
|
||||
<td>
|
||||
{{if $item.Admin}}<span class="badge badge-danger">admin</span>{{end}}
|
||||
{{if $item.Secret}}<span class="badge badge-success">totp</span>{{end}}
|
||||
{{if eq $item.Locked 3}}<span class="badge badge-warning">locked</span>{{end}}
|
||||
{{if $item.Admin}}<span class="badge badge-danger">admin</span>{{end}}
|
||||
{{if $item.Disabled}}<span class="badge badge-warning">disabled</span>{{end}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
{{if $admin}}
|
||||
<a class="btn btn-sm btn-primary" href="/user/sessions/{{$item.Username}}">Sessions</a>
|
||||
<a class="btn btn-sm btn-primary" href="/user/edit/{{$item.Username}}">Edit</a>
|
||||
<a class="btn btn-sm btn-danger" href="/user/del/{{$item.Username}}">Delete</a>
|
||||
{{else}}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ cat <<EOF> /var/acc/goacc.conf
|
|||
"listen": "127.0.0.1:4443",
|
||||
"secret": "$(openssl rand -hex 32)",
|
||||
"hydra_admin_url": "http://127.0.0.1:4445",
|
||||
"admin": true,
|
||||
"admin_disabled": false,
|
||||
"secure_cookie": true
|
||||
}
|
||||
EOF
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue