Tokens: User improvements
This commit is contained in:
parent
8ccd490bed
commit
4f4fa2b3d7
6 changed files with 117 additions and 24 deletions
|
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<p>
|
||||||
|
A new token has been created.
|
||||||
|
This token will only be shown once.
|
||||||
|
Please make sure to copy it now.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<p>
|
||||||
|
<span class="copy">{{ .Token.Token }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<p>
|
||||||
|
To test this token, you can use curl on the command line:
|
||||||
|
</p>
|
||||||
|
<code class="copy">
|
||||||
|
curl -H "Authorization: Bearer <b>{{ .Token.Token }}</b>" {{ .Domain }}api/v1/auth
|
||||||
|
</code>
|
||||||
|
<p>
|
||||||
|
You should receive a response with <code>token: true</code> inside of it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<a href="/user/tokens/" class="pure-button">Back To Token List</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-2-3">
|
||||||
<h2>My Tokens</h2>
|
<h2>My Tokens</h2>
|
||||||
<p>
|
<p>
|
||||||
This table shows tokens currently associated with your account.
|
This table shows tokens currently associated with your account.
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
<table class="pure-table pure-table-bordered">
|
<table class="pure-table pure-table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>
|
||||||
|
ID
|
||||||
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Token
|
Token
|
||||||
</th>
|
</th>
|
||||||
|
|
@ -32,7 +35,10 @@
|
||||||
{{ range .Tokens }}
|
{{ range .Tokens }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code class="copy">{{ .Token }}</code>
|
<code class="copy">{{ .TokenID }}</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
(only shown once)
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ .Description }}
|
{{ .Description }}
|
||||||
|
|
@ -40,7 +46,7 @@
|
||||||
<td>
|
<td>
|
||||||
<div class="pure-button-group" role="group">
|
<div class="pure-button-group" role="group">
|
||||||
<form action="/user/tokens/delete" method="POST" class="pure-form-group">
|
<form action="/user/tokens/delete" method="POST" class="pure-form-group">
|
||||||
<input type="hidden" name="token" value="{{ .Token }}">
|
<input type="hidden" name="id" value="{{ .TokenID }}">
|
||||||
<input type="submit" class="pure-button pure-button-danger" value="Delete">
|
<input type="submit" class="pure-button pure-button-danger" value="Delete">
|
||||||
{{ $csrf }}
|
{{ $csrf }}
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -54,3 +60,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-u-1-3">
|
||||||
|
<p>
|
||||||
|
To check if a token is working, you can use something like:
|
||||||
|
</p>
|
||||||
|
<code class="copy">
|
||||||
|
curl -H "Authorization: Bearer <b><token></b>" {{ .Domain }}api/v1/auth
|
||||||
|
</code>
|
||||||
|
<p>
|
||||||
|
When using a working token, you should get a response with <code>Token: true</code> in it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
@ -2,6 +2,7 @@ package panel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
|
@ -27,6 +28,7 @@ var tokensTemplate = templating.Parse[TokenTemplateContext](
|
||||||
type TokenTemplateContext struct {
|
type TokenTemplateContext struct {
|
||||||
templating.RuntimeFlags
|
templating.RuntimeFlags
|
||||||
|
|
||||||
|
Domain template.URL // server base URL
|
||||||
Tokens []models.Token
|
Tokens []models.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,6 +51,8 @@ func (panel *UserPanel) tokensRoute(ctx context.Context) http.Handler {
|
||||||
return tc, err
|
return tc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tc.Domain = template.URL(panel.Config.HTTP.JoinPath().String())
|
||||||
|
|
||||||
// get the tokens
|
// get the tokens
|
||||||
tc.Tokens, err = panel.Dependencies.Tokens.Tokens(r.Context(), user.User.User)
|
tc.Tokens, err = panel.Dependencies.Tokens.Tokens(r.Context(), user.User.User)
|
||||||
return tc, err
|
return tc, err
|
||||||
|
|
@ -70,14 +74,14 @@ func (panel *UserPanel) tokensDeleteRoute(ctx context.Context) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := r.PostFormValue("token")
|
id := r.PostFormValue("id")
|
||||||
if token == "" {
|
if id == "" {
|
||||||
logger.Err(err).Str("action", "delete token").Msg("failed to get token")
|
logger.Err(err).Str("action", "delete token").Msg("failed to get token")
|
||||||
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
|
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := panel.Dependencies.Tokens.Remove(r.Context(), user.User.User, token); err != nil {
|
if err := panel.Dependencies.Tokens.Remove(r.Context(), user.User.User, id); err != nil {
|
||||||
logger.Err(err).Str("action", "delete token").Msg("failed to delete token")
|
logger.Err(err).Str("action", "delete token").Msg("failed to delete token")
|
||||||
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
|
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
|
@ -101,8 +105,32 @@ type addTokenResult struct {
|
||||||
Scopes []string
|
Scopes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed "templates/token_created.html"
|
||||||
|
var tokenCreatedHTML []byte
|
||||||
|
var tokenCreateTemplate = templating.Parse[TokenCreateContext](
|
||||||
|
"token_created.html", tokenCreatedHTML, httpx.FormTemplate,
|
||||||
|
templating.Title("Add Token"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenCreateContext struct {
|
||||||
|
templating.RuntimeFlags
|
||||||
|
|
||||||
|
Domain template.URL // server base URL
|
||||||
|
Token *models.Token
|
||||||
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
|
func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
|
||||||
tpl := tokensAddTemplate.Prepare(
|
tplForm := tokensAddTemplate.Prepare(
|
||||||
|
panel.Dependencies.Templating,
|
||||||
|
templating.Crumbs(
|
||||||
|
menuUser,
|
||||||
|
menuTokens,
|
||||||
|
menuTokensAdd,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
tplDone := tokenCreateTemplate.Prepare(
|
||||||
panel.Dependencies.Templating,
|
panel.Dependencies.Templating,
|
||||||
templating.Crumbs(
|
templating.Crumbs(
|
||||||
menuUser,
|
menuUser,
|
||||||
|
|
@ -117,8 +145,8 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: tpl.Template(),
|
RenderTemplate: tplForm.Template(),
|
||||||
RenderTemplateContext: templating.FormTemplateContext(tpl),
|
RenderTemplateContext: templating.FormTemplateContext(tplForm),
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (at addTokenResult, err error) {
|
Validate: func(r *http.Request, values map[string]string) (at addTokenResult, err error) {
|
||||||
at.User, err = panel.Dependencies.Auth.UserOfSession(r)
|
at.User, err = panel.Dependencies.Auth.UserOfSession(r)
|
||||||
|
|
@ -138,16 +166,19 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
|
||||||
|
|
||||||
RenderSuccess: func(at addTokenResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
|
RenderSuccess: func(at addTokenResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
|
||||||
// add the key to the user
|
// add the key to the user
|
||||||
_, err := panel.Dependencies.Tokens.Add(r.Context(), at.User.User.User, at.Description, at.Scopes)
|
tok, err := panel.Dependencies.Tokens.Add(r.Context(), at.User.User.User, at.Description, at.Scopes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errAddToken
|
return errAddToken
|
||||||
}
|
}
|
||||||
// everything went fine, redirect the user back to the user page!
|
|
||||||
http.Redirect(w, r, string(menuTokens.Path), http.StatusSeeOther)
|
// render the created context
|
||||||
return nil
|
return httpx.WriteHTML(tplDone.Context(r, TokenCreateContext{
|
||||||
|
Domain: template.URL(panel.Config.HTTP.JoinPath().String()),
|
||||||
|
Token: tok,
|
||||||
|
}), nil, tplDone.Template(), "", w, r)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,18 +41,22 @@ func (g GrantWithURL) AdminURL() template.URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
|
actions := []component.MenuItem{
|
||||||
|
menuChangePassword,
|
||||||
|
menuTOTPAction,
|
||||||
|
menuSSH,
|
||||||
|
}
|
||||||
|
|
||||||
|
if panel.Config.HTTP.API.Value {
|
||||||
|
actions = append(actions, menuTokens)
|
||||||
|
}
|
||||||
|
|
||||||
tpl := userTemplate.Prepare(
|
tpl := userTemplate.Prepare(
|
||||||
panel.Dependencies.Templating,
|
panel.Dependencies.Templating,
|
||||||
templating.Crumbs(
|
templating.Crumbs(
|
||||||
menuUser,
|
menuUser,
|
||||||
),
|
),
|
||||||
templating.Actions(
|
templating.Actions(actions...),
|
||||||
menuChangePassword,
|
|
||||||
menuTOTPAction,
|
|
||||||
menuSSH,
|
|
||||||
menuTokens,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (uc userContext, funcs []templating.FlagFunc, err error) {
|
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (uc userContext, funcs []templating.FlagFunc, err error) {
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,16 @@ func (tok *Tokens) Add(ctx context.Context, user string, description string, sco
|
||||||
}
|
}
|
||||||
mk.SetScopes(scopes)
|
mk.SetScopes(scopes)
|
||||||
|
|
||||||
// generate a new random password
|
// generate a new id for the token
|
||||||
|
{
|
||||||
|
var err error
|
||||||
|
mk.TokenID, err = NewToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the actual token
|
||||||
var err error
|
var err error
|
||||||
mk.Token, err = NewToken()
|
mk.Token, err = NewToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -136,7 +145,7 @@ func (tok *Tokens) Add(ctx context.Context, user string, description string, sco
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes a token with the given token from the user
|
// Remove removes a token with the given token from the user
|
||||||
func (tok *Tokens) Remove(ctx context.Context, user, token string) error {
|
func (tok *Tokens) Remove(ctx context.Context, user, id string) error {
|
||||||
// get the table
|
// get the table
|
||||||
table, err := tok.table(ctx)
|
table, err := tok.table(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -144,5 +153,5 @@ func (tok *Tokens) Remove(ctx context.Context, user, token string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// and do the delete
|
// and do the delete
|
||||||
return table.Where("user = ? AND token = ?", user, token).Delete(&models.Token{}).Error
|
return table.Where("user = ? AND id = ?", user, id).Delete(&models.Token{}).Error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ const TokensTable = "tokens"
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Pk uint `gorm:"column:pk;primaryKey"`
|
Pk uint `gorm:"column:pk;primaryKey"`
|
||||||
|
|
||||||
Token string `gorm:"column:token;unique:true;not null"`
|
Token string `json:"-" gorm:"column:token;unique:true;not null"` // token used by the actual api (shown only once)
|
||||||
|
TokenID string `gorm:"column:id;unique:true;not null"` // token id (displayed to user, used for finding it)
|
||||||
|
|
||||||
User string `gorm:"column:user;not null"` // (distillery) username
|
User string `gorm:"column:user;not null"` // (distillery) username
|
||||||
|
|
||||||
Description string `gorm:"column:description"`
|
Description string `gorm:"column:description"`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue