add user creation
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
leo 2023-05-22 06:47:33 +02:00
parent cca2b360f4
commit 547f6e7b3c
Signed by: wanderer
SSH Key Fingerprint: SHA256:Dp8+iwKHSlrMEHzE3bJnPng70I7LEsa3IJXRH/U+idQ
6 changed files with 315 additions and 20 deletions

@ -49,6 +49,8 @@ func (a *App) SetupRoutes() {
e.GET("/home", handlers.Home(a.db)) e.GET("/home", handlers.Home(a.db))
e.GET("/manage/users", handlers.ManageUsers(a.db)) e.GET("/manage/users", handlers.ManageUsers(a.db))
e.GET("/manage/users/new", handlers.ManageUsers(a.db))
e.POST("/manage/users/create", handlers.CreateUser(a.db))
e.GET("/logout", handlers.Logout()) e.GET("/logout", handlers.Logout())
e.POST("/logout", handlers.Logout()) e.POST("/logout", handlers.Logout())

File diff suppressed because one or more lines are too long

@ -13,7 +13,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func ManageUsers(client *ent.Client) echo.HandlerFunc { func ManageUsers(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
return func(c echo.Context) error { return func(c echo.Context) error {
addHeaders(c) addHeaders(c)
@ -77,6 +77,46 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc {
) )
} }
data := make(map[string]any)
flash := sess.Values["flash"]
path := c.Request().URL.Path
if path == "/manage/users/new" {
p := page{
AppName: setting.AppName(),
AppVer: appver,
Title: "Manage Users - New User",
CSRF: c.Get("csrf").(string),
DevelMode: setting.IsDevel(),
Current: "manage-users-new",
User: u,
}
if flash != nil {
data["flash"] = flash.(string)
delete(sess.Values, "flash")
_ = sess.Save(c.Request(), c.Response())
}
err := c.Render(
http.StatusOK,
"manage/user-new.tmpl",
p,
)
if err != nil {
return renderErrorPage(
c,
http.StatusInternalServerError,
http.StatusText(http.StatusInternalServerError),
err.Error(),
)
}
return nil
}
var allUsers []*moduser.User var allUsers []*moduser.User
if users, err := moduser.ListAll(ctx, client); err == nil && users != nil { if users, err := moduser.ListAll(ctx, client); err == nil && users != nil {
@ -104,23 +144,31 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc {
) )
} }
data := make(map[string]any)
data["allusers"] = allUsers data["allusers"] = allUsers
p := page{
AppName: setting.AppName(),
AppVer: appver,
Title: "Manage Users",
CSRF: c.Get("csrf").(string),
DevelMode: setting.IsDevel(),
Current: "manage-users",
User: u,
Data: data,
}
if flash != nil {
data["flash"] = flash.(string)
delete(sess.Values, "flash")
_ = sess.Save(c.Request(), c.Response())
}
err := c.Render( err := c.Render(
http.StatusOK, http.StatusOK,
"manage/user.tmpl", "manage/user.tmpl",
page{ p,
AppName: setting.AppName(),
AppVer: appver,
Title: "Manage Users",
CSRF: c.Get("csrf").(string),
DevelMode: setting.IsDevel(),
Current: "manage-users",
User: u,
Data: data,
},
) )
if err != nil { if err != nil {
return renderErrorPage( return renderErrorPage(
@ -134,3 +182,146 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc {
return nil return nil
} }
} }
func CreateUser(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
return func(c echo.Context) error {
addHeaders(c)
sess, _ := session.Get(setting.SessionCookieName(), c)
if sess == nil {
c.Logger().Info("No session found, unauthorised.")
return renderErrorPage(
c,
http.StatusUnauthorized,
http.StatusText(http.StatusUnauthorized),
"No session found, please log in",
)
}
uname := sess.Values["username"]
if uname == nil {
c.Logger().Debugf("%d - %s", http.StatusUnauthorized, "session expired or invalid")
return renderErrorPage(
c,
http.StatusUnauthorized,
http.StatusText(http.StatusUnauthorized)+": Log in again",
"Session expired, log in again.",
)
}
log.Info("gorilla session", "username", sess.Values["username"].(string))
username := sess.Values["username"].(string)
var u moduser.User
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
if usr, err := moduser.QueryUser(ctx, client, username); err == nil && usr != nil {
u.ID = usr.ID
u.Username = usr.Username
u.IsAdmin = usr.IsAdmin
u.CreatedAt = usr.CreatedAt
u.IsActive = usr.IsActive
u.IsLoggedIn = true
} else {
c.Logger().Error(http.StatusText(http.StatusInternalServerError) + " - " + err.Error())
return renderErrorPage(
c,
http.StatusInternalServerError,
http.StatusText(http.StatusInternalServerError),
err.Error(),
)
}
if !u.IsAdmin {
c.Logger().Debug("this is a restricted endpoint")
status := http.StatusUnauthorized
msg := http.StatusText(status)
return renderErrorPage(
c, status, msg+": You should not be here", "Restricted endpoint",
)
}
uc := new(userCreate)
if err := c.Bind(uc); err != nil {
return renderErrorPage(
c,
http.StatusBadRequest,
http.StatusText(http.StatusBadRequest),
err.Error(),
)
}
if uc.Username == "" || uc.Password == "" || uc.RepeatPassword == "" || uc.Password != uc.RepeatPassword {
c.Logger().Error("username or password not set, returning to /manage/users/new")
msg := "Error: both username and password need to be set"
if uc.Password != uc.RepeatPassword {
msg += "; password needs to be passed the same twice"
}
data := make(map[string]any)
data["flash"] = msg
data["form"] = uc
return c.Render(
http.StatusBadRequest,
"manage/user-new.tmpl",
page{
AppName: setting.AppName(),
AppVer: appver,
Title: "Manage Users - New User",
DevelMode: setting.IsDevel(),
Current: "manage-user-new",
Data: data,
},
)
}
var msg string
usr, err := moduser.CreateUser(ctx, client, uc.Email, uc.Username, uc.Password, uc.IsAdmin)
if err == nil && usr != nil {
msg = "created user '" + usr.Username + "'!"
sess.Values["flash"] = msg
_ = sess.Save(c.Request(), c.Response())
return c.Redirect(http.StatusSeeOther, "/manage/users")
}
if ent.IsNotSingular(err) {
c.Logger().Error("user exists")
msg = "Error: user already exists: " + err.Error()
} else {
msg = "Error: " + err.Error()
}
data := make(map[string]any)
data["flash"] = msg
data["form"] = uc
return c.Render(
http.StatusInternalServerError,
"manage/user-new.tmpl",
page{
AppName: setting.AppName(),
AppVer: appver,
Title: "Manage Users",
DevelMode: setting.IsDevel(),
Current: "manage-user",
Data: data,
},
)
}
}

@ -13,3 +13,12 @@ type userSignup struct {
Email string `form:"email" json:"email"` Email string `form:"email" json:"email"`
Password string `form:"password" json:"password"` Password string `form:"password" json:"password"`
} }
type userCreate struct {
Username string `form:"username" json:"username" validate:"required,username"`
Email string `form:"email" json:"email" validate:"required,email"`
Password string `form:"password" json:"password" validate:"required,password"`
RepeatPassword string `form:"repeatPassword" json:"repeatPassword" validate:"required,repeatPassword"`
IsAdmin bool `form:"isAdmin" json:"isAdmin" validate:"required,isAdmin"`
IsActive *bool `form:"isActive" json:"isActive" validate:"omitempty,isActive"`
}

@ -0,0 +1,88 @@
{{ template "head.tmpl" . }}
<body class="min-h-screen bg-white dark:bg-gray-900">
{{ template "navbar.tmpl" . }}
<main class="grow min-w-[300px]">
<section class="bg-white dark:bg-gray-900">
<div class="container mx-auto md:mx-4 px-6 sm:px-0:py-0 md:py-16 lg:py-32">
<div class="lg:flex">
<div class="lg:w-1/2">
<h1 class="mt-4 text-2xl font-medium text-gray-800 capitalize lg:text-3xl dark:text-white">
Create new user
</h1>
{{ if and .Data .Data.flash }}
<h2 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
{{- .Data.flash -}}
</h2>
{{- end }}
</div>
<div class="mt-8 lg:w-1/2 lg:mt-0">
<form method="post" action="/manage/users/create" class="w-full lg:max-w-xl">
<input type="hidden" name="_csrf" value="{{- .CSRF -}}">
<div class="relative flex items-center">
<span class="absolute" role="img" aria-label="person outline icon for username">
{{ template "svg-user.tmpl" }}
</span>
<input name="username" type="text" {{if and .Data.form .Data.form.Username}}value="{{.Data.form.Username}}"{{end}} placeholder="Username" required class="block w-full py-3 valid:border-green-300 required:border-blue-300 text-gray-700 bg-white border rounded-lg px-11 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
</div>
<div class="relative flex items-center mt-4">
<span class="absolute" role="img" aria-label="email icon">
{{ template "svg-email.tmpl" }}
</span>
<input name="email" type="email" {{if and .Data.form .Data.form.Email}}value="{{.Data.form.Email}}"{{end}} placeholder="Email address" required class="peer block w-full px-10 py-3 required:border-blue-300 text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
<p class="mt-2 mx-4 peer-placeholder-shown:collapse peer-invalid:block text-pink-600 text-sm">
Please provide a valid email address.
</p>
</div>
<div class="relative flex items-center mt-4">
<span class="absolute" role="img" aria-label="password lock icon">
{{ template "svg-password.tmpl" }}
</span>
<input name="password" type="password" {{if and .Data.form .Data.form.Password}}value="{{.Data.form.Password}}"{{end}} placeholder="Password" required class="block w-full px-10 py-3 required:border-blue-300 text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
</div>
<div class="relative flex items-center mt-4">
<span class="absolute" role="img" aria-label="password lock icon">
{{ template "svg-password.tmpl" }}
</span>
<input name="repeatPassword" type="password" {{if and .Data.form .Data.form.RepeatPassword}}value="{{.Data.form.RepeatPassword}}"{{end}} placeholder="Repeat Password" required class="block w-full px-10 py-3 required:border-blue-300 text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
</div>
<div class="flex pt-2 px-2 items-center justify-center gap-6">
<div class="mb-1 block min-h-3">
<input
type="checkbox"
value="true"
id="isAdmin"
name="isAdmin"
/>
<label
class="inline-block pl-1 hover:cursor-pointer text-gray-700 dark:text-gray-300"
for="isAdmin">
Is admin?
</label>
</div>
<div class="mb-1 block min-h-3">
<input
type="checkbox"
value="true"
id="isActive"
name="isActive"
/>
<label
class="inline-block pl-1 hover:cursor-pointer text-gray-700 dark:text-gray-300"
for="isActive">
Is active?
</label>
</div>
</div>
<div class="mt-8 md:flex md:items-center">
<button class="w-full px-6 py-3 text-sm font-medium tracking-wide text-white capitalize transition-colors duration-300 transform bg-blue-500 rounded-lg md:w-1/2 hover:bg-blue-400 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-50">
Create
</button>
</div>
</form>
</div>
</div>
</div>
</section>
</main>
{{ template "footer.tmpl" . }}

@ -3,22 +3,27 @@
{{ template "navbar.tmpl" . }} {{ template "navbar.tmpl" . }}
<main class="grow"> <main class="grow">
<div class="container mx-auto place-items-center px-8"> <div class="container mx-auto place-items-center px-8">
{{ if and .Data .Data.flash }}
<h1 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
{{ .Data.flash }}
</h1>
{{- end }}
<div class="flex justify-between place-items-center"> <div class="flex justify-between place-items-center">
<h1 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2"> <h1 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
Manage users Manage users
</h1> </h1>
<a href="/manage/users/new" class="w-auto py-1 mt-2 text-center text-blue-500 md:mt-0 md:mx-6 lg:mx-auto hover:underline dark:text-blue-400"> <a href="/manage/users/new" class="w-auto py-1 mt-2 text-center text-blue-500 md:mt-0 md:mx-6 lg:mx-4 hover:underline dark:text-blue-400">
New user New user
</a> </a>
</div> </div>
<div class="p-2 mt-3 border-2 dark:border-slate-500 rounded-sm"> <div class="p-2 mt-3 border-2 dark:border-slate-500 rounded-sm overflow-x-auto">
<table class="text-center text-gray-500 dark:text-gray-300 w-full"> <table class="text-center text-gray-500 dark:text-gray-300 w-full">
<thead> <thead>
<tr> <tr>
<th scope="col" class="p-2 text-slate-600 dark:text-slate-400">Username</th> <th scope="col" class="md:p-2 sm:p-0 text-slate-600 dark:text-slate-400">Username</th>
<th scope="col" class="p-2 text-left text-slate-600 dark:text-slate-400">Email</th> <th scope="col" class="md:p-2 sm:p-0 text-left text-slate-600 dark:text-slate-400">Email</th>
<th scope="col" class="p-2 text-slate-600 dark:text-slate-400">Admin</th> <th scope="col" class="md:p-2 sm:p-0 sm:px-1 text-slate-600 dark:text-slate-400">Admin</th>
<th scope="col" class="p-2 text-slate-600 dark:text-slate-400">Active</th> <th scope="col" class="md:p-2 sm:p-0 text-slate-600 dark:text-slate-400">Active</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>