Add: CreateBackendAccess function
This commit is contained in:
@@ -187,6 +187,37 @@ export const actions = {
|
|||||||
return fail(400, { errors: errors });
|
return fail(400, { errors: errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const response = await res.json();
|
||||||
|
console.log('Server success response:', response);
|
||||||
|
throw redirect(303, `${base}/auth/admin/users`);
|
||||||
|
},
|
||||||
|
grantBackendAccess: async ({ request, fetch, cookies }) => {
|
||||||
|
let formData = await request.formData();
|
||||||
|
|
||||||
|
const rawData = formDataToObject(formData);
|
||||||
|
const processedData = processUserFormData(rawData);
|
||||||
|
console.dir(processedData);
|
||||||
|
const apiURL = `${BASE_API_URI}/auth/users/activate`;
|
||||||
|
|
||||||
|
/** @type {RequestInit} */
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'PATCH',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: `jwt=${cookies.get('jwt')}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify(processedData)
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await fetch(apiURL, requestOptions);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const response = await res.json();
|
||||||
|
const errors = formatError(response.errors);
|
||||||
|
return fail(400, { errors: errors });
|
||||||
|
}
|
||||||
|
|
||||||
const response = await res.json();
|
const response = await res.json();
|
||||||
console.log('Server success response:', response);
|
console.log('Server success response:', response);
|
||||||
throw redirect(303, `${base}/auth/admin/users`);
|
throw redirect(303, `${base}/auth/admin/users`);
|
||||||
|
|||||||
@@ -234,6 +234,41 @@
|
|||||||
<th>{$t('user.id')}</th>
|
<th>{$t('user.id')}</th>
|
||||||
<td>{user.id}</td>
|
<td>{user.id}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/grantBackendAccess"
|
||||||
|
use:enhance={() => {
|
||||||
|
return async ({ result }) => {
|
||||||
|
if (result.type === 'success' || result.type === 'redirect') {
|
||||||
|
await applyAction(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
on:submit|preventDefault={(/** @type {SubmitEvent} */ e) => {
|
||||||
|
if (
|
||||||
|
!confirm(
|
||||||
|
$t('dialog.backend_access', {
|
||||||
|
values: {
|
||||||
|
firstname: user.first_name || '',
|
||||||
|
lastname: user.last_name || ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
e.preventDefault(); // Cancel form submission if user declines
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="user[id]" value={user.id} />
|
||||||
|
<button class="button-dark" type="submit">
|
||||||
|
<i class="fas fa-unlock-keyhole"></i>
|
||||||
|
{$t('grant_backend_access')}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('name')}</th>
|
<th>{$t('name')}</th>
|
||||||
<td>{user.first_name} {user.last_name}</td>
|
<td>{user.first_name} {user.last_name}</td>
|
||||||
@@ -568,6 +603,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap; /* Allows wrapping on small screens */
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const (
|
|||||||
AwaitingPaymentStatus
|
AwaitingPaymentStatus
|
||||||
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
|
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
|
||||||
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
||||||
|
MailGrantBackendAccessSubject = "Dein Dörpsmobil Hasloh e.V. Zugang"
|
||||||
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
||||||
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
||||||
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
||||||
@@ -66,11 +67,13 @@ var Priviliges = struct {
|
|||||||
Create int8
|
Create int8
|
||||||
Update int8
|
Update int8
|
||||||
Delete int8
|
Delete int8
|
||||||
|
AccessControl int8
|
||||||
}{
|
}{
|
||||||
View: 2,
|
View: 2,
|
||||||
Update: 4,
|
Update: 4,
|
||||||
Create: 4,
|
Create: 4,
|
||||||
Delete: 4,
|
Delete: 4,
|
||||||
|
AccessControl: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Roles = struct {
|
var Roles = struct {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"GoMembership/internal/constants"
|
"GoMembership/internal/constants"
|
||||||
"GoMembership/internal/utils"
|
"GoMembership/internal/utils"
|
||||||
"GoMembership/pkg/errors"
|
"GoMembership/pkg/errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -12,6 +13,55 @@ import (
|
|||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (uc *UserController) CreatePasswordHandler(c *gin.Context) {
|
||||||
|
|
||||||
|
requestUser, err := uc.ExtractUserFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !utils.HasPrivilige(requestUser, constants.Priviliges.AccessControl) {
|
||||||
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to handle all users. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.View), http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Expected data from the user
|
||||||
|
var input struct {
|
||||||
|
User struct {
|
||||||
|
ID uint `json:"id" binding:"required,numeric"`
|
||||||
|
} `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
utils.HandleValidationError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// find user
|
||||||
|
db_user, err := uc.Service.GetUserByID(input.User.ID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "couldn't get user by id", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create token
|
||||||
|
token, err := uc.Service.HandlePasswordChangeRequest(db_user)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "couldn't handle password change request", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email
|
||||||
|
if err := uc.EmailService.SendGrantBackendAccessEmail(db_user, &token); err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Couldn't send grant backend access email", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusAccepted, gin.H{
|
||||||
|
"message": "password_change_requested",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (uc *UserController) RequestPasswordChangeHandler(c *gin.Context) {
|
func (uc *UserController) RequestPasswordChangeHandler(c *gin.Context) {
|
||||||
|
|
||||||
// Expected data from the user
|
// Expected data from the user
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func (uc *UserController) DeleteUser(c *gin.Context) {
|
|||||||
|
|
||||||
type deleteData struct {
|
type deleteData struct {
|
||||||
User struct {
|
User struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id" binding:"required,numeric"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
} `json:"user"`
|
} `json:"user"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
|
|||||||
userRouter.POST("/users", userController.RegisterUser)
|
userRouter.POST("/users", userController.RegisterUser)
|
||||||
userRouter.GET("/users", userController.GetAllUsers)
|
userRouter.GET("/users", userController.GetAllUsers)
|
||||||
userRouter.DELETE("/users", userController.DeleteUser)
|
userRouter.DELETE("/users", userController.DeleteUser)
|
||||||
|
userRouter.PATCH("/users/activate", userController.CreatePasswordHandler)
|
||||||
userRouter.GET("/subscriptions", membershipcontroller.GetSubscriptions)
|
userRouter.GET("/subscriptions", membershipcontroller.GetSubscriptions)
|
||||||
userRouter.PUT("/subscriptions", membershipcontroller.UpdateHandler)
|
userRouter.PUT("/subscriptions", membershipcontroller.UpdateHandler)
|
||||||
userRouter.POST("/subscriptions", membershipcontroller.RegisterSubscription)
|
userRouter.POST("/subscriptions", membershipcontroller.RegisterSubscription)
|
||||||
|
|||||||
@@ -87,6 +87,37 @@ func (s *EmailService) SendVerificationEmail(user *models.User, token *string) e
|
|||||||
return s.SendEmail(user.Email, subject, body, "", "")
|
return s.SendEmail(user.Email, subject, body, "", "")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func (s *EmailService) SendGrantBackendAccessEmail(user *models.User, token *string) error {
|
||||||
|
// Prepare data to be injected into the template
|
||||||
|
data := struct {
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Token string
|
||||||
|
BASEURL string
|
||||||
|
FRONTEND_PATH string
|
||||||
|
UserID uint
|
||||||
|
}{
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
Token: *token,
|
||||||
|
FRONTEND_PATH: config.Site.FrontendPath,
|
||||||
|
BASEURL: config.Site.BaseURL,
|
||||||
|
UserID: user.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
subject := constants.MailGrantBackendAccessSubject
|
||||||
|
htmlBody, err := ParseTemplate("mail_grant_backend_access.tmpl", data)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Print("Couldn't send grant backend access mail")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
plainBody, err := ParseTemplate("mail_grant_backend_access.txt.tmpl", data)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Print("Couldn't parse password mail")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.SendEmail(user.Email, subject, htmlBody, plainBody, "")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string) error {
|
func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string) error {
|
||||||
// Prepare data to be injected into the template
|
// Prepare data to be injected into the template
|
||||||
|
|||||||
177
go-backend/templates/email/mail_grant_backend_access.tmpl
Normal file
177
go-backend/templates/email/mail_grant_backend_access.tmpl
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
background-color: #f2f5f7;
|
||||||
|
color: #242424;
|
||||||
|
font-family: Optima, Candara, "Noto Sans", source-sans-pro,
|
||||||
|
sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.15008px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 32px 0;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
align="center"
|
||||||
|
width="100%"
|
||||||
|
style="margin: 0 auto; max-width: 600px; background-color: #ffffff"
|
||||||
|
role="presentation"
|
||||||
|
cellspacing="0"
|
||||||
|
cellpadding="0"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr style="width: 100%">
|
||||||
|
<td>
|
||||||
|
<div style="padding: 24px 24px 24px 24px; text-align: center">
|
||||||
|
<a
|
||||||
|
href="{{.BASEURL}}"
|
||||||
|
style="text-decoration: none"
|
||||||
|
target="_blank"
|
||||||
|
><img
|
||||||
|
alt="Dörpsmobil Hasloh"
|
||||||
|
src="{{.BASEURL}}/images/CarsharingSH-Hasloh-LOGO.jpeg"
|
||||||
|
style="
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
"
|
||||||
|
/></a>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 0px 24px 16px 24px">
|
||||||
|
Moin {{.FirstName}} {{.LastName}} 👋,
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 0px 24px 16px 24px">
|
||||||
|
hiermit erhältst Du Zugnag zu Deinem Dörpsmobil Hasloh Account.
|
||||||
|
</div>
|
||||||
|
<div style="padding: 16px 0px 16px 0px">
|
||||||
|
<hr
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #cccccc;
|
||||||
|
margin: 0;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 16px 24px; text-align: center; font-family: Arial, sans-serif; color: #333;">
|
||||||
|
<p style="margin-bottom: 16px;">
|
||||||
|
Mit diesem Link kannst Du Dich dann in Deinen <strong>Dörpsmobil Hasloh</strong> Account anmelden:
|
||||||
|
</p>
|
||||||
|
<p style="margin: 16px 0; font-size: 18px;">
|
||||||
|
<a href="{{.BASEURL}}{{.FRONTEND_PATH}}/"
|
||||||
|
style="display: inline-block; padding: 12px 20px; background-color: #007bff; color: white; text-decoration: none; font-weight: bold; border-radius: 5px;">
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin-top: 16px;">
|
||||||
|
Dafür musst Du aber zunächst noch Dein Passwort erstellen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: center; padding: 16px 24px 16px 24px">
|
||||||
|
<a
|
||||||
|
href=" {{.BASEURL}}{{.FRONTEND_PATH}}/auth/password/change/{{.UserID}}?token={{.Token}}"
|
||||||
|
style="
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #3e9bfc;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
padding: 16px 32px;
|
||||||
|
text-decoration: none;
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
><span
|
||||||
|
><!--[if mso
|
||||||
|
]><i
|
||||||
|
style="
|
||||||
|
letter-spacing: 32px;
|
||||||
|
mso-font-width: -100%;
|
||||||
|
mso-text-raise: 48;
|
||||||
|
"
|
||||||
|
hidden
|
||||||
|
> </i
|
||||||
|
><!
|
||||||
|
[endif]--></span
|
||||||
|
><span>
|
||||||
|
Passwort erstellen
|
||||||
|
</span
|
||||||
|
><span
|
||||||
|
><!--[if mso
|
||||||
|
]><i
|
||||||
|
style="letter-spacing: 32px; mso-font-width: -100%"
|
||||||
|
hidden
|
||||||
|
> </i
|
||||||
|
><!
|
||||||
|
[endif]--></span
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 24px 0px 24px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Alternativ kannst Du auch diesen Link in Deinem Browser öffnen:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4px 24px 16px 24px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/auth/password/change/{{.UserID}}?token={{.Token}}
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 16px 24px 16px 24px">
|
||||||
|
Mit Freundlichen Grüßen,
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
padding: 16px 24px 16px 24px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Der Vorstand
|
||||||
|
</div>
|
||||||
|
<div style="padding: 16px 24px 16px 24px">
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src="{{.BASEURL}}/images/favicon_hu5543b2b337a87a169e2c722ef0122802_211442_96x0_resize_lanczos_3.png"
|
||||||
|
height="80"
|
||||||
|
width="80"
|
||||||
|
style="
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
text-decoration: none;
|
||||||
|
object-fit: cover;
|
||||||
|
height: 80px;
|
||||||
|
width: 80px;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 80px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
Moin {{.FirstName}} {{.LastName}} 👋,
|
||||||
|
|
||||||
|
hiermit erhältst Du Zugnag zu Deinem Dörpsmobil Hasloh Account.
|
||||||
|
|
||||||
|
Mit diesem Link kannst Du Dich dann in Deinen Dörpsmobil Hasloh Account anmelden:
|
||||||
|
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/
|
||||||
|
|
||||||
|
einloggen.
|
||||||
|
|
||||||
|
Dafür musst Du aber zunächst noch Dein Passwort erstellen:
|
||||||
|
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/auth/password/change/{{.UserID}}?token={{.Token}}
|
||||||
|
|
||||||
|
Mit Freundlichen Grüßen,
|
||||||
|
|
||||||
|
Der Vorstand
|
||||||
Reference in New Issue
Block a user