Compare commits
4 Commits
8d56a9ad48
...
c810e48451
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c810e48451 | ||
|
|
8f737282f2 | ||
|
|
3756205ad4 | ||
|
|
68851c6257 |
@@ -99,14 +99,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active tab
|
||||
* @param {string} tab - The tab to set as active
|
||||
*/
|
||||
function setActiveTab(tab) {
|
||||
activeTab = tab;
|
||||
}
|
||||
|
||||
/** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */
|
||||
const handleUpdate = async () => {
|
||||
isUpdating = true;
|
||||
@@ -158,7 +150,7 @@
|
||||
type="button"
|
||||
class="button-dark"
|
||||
class:active={activeTab === tab}
|
||||
on:click={() => setActiveTab(tab)}
|
||||
on:click={() => (activeTab = tab)}
|
||||
>
|
||||
{$t(tab)}
|
||||
</button>
|
||||
|
||||
@@ -154,7 +154,8 @@ export default {
|
||||
},
|
||||
dialog: {
|
||||
user_deletion: 'Soll der Nutzer {firstname} {lastname} wirklich gelöscht werden?',
|
||||
subscription_deletion: 'Soll das Tarifmodell {name} wirklich gelöscht werden?'
|
||||
subscription_deletion: 'Soll das Tarifmodell {name} wirklich gelöscht werden?',
|
||||
backend_access: 'Soll {firstname} {lastname} Backend Zugriff gewährt werden?'
|
||||
},
|
||||
cancel: 'Abbrechen',
|
||||
confirm: 'Bestätigen',
|
||||
@@ -163,6 +164,7 @@ export default {
|
||||
delete: 'Löschen',
|
||||
search: 'Suche:',
|
||||
name: 'Name',
|
||||
grant_backend_access: 'Backend Zugriff gewähren',
|
||||
supporter: 'Sponsoren',
|
||||
mandate_date_signed: 'Mandatserteilungsdatum',
|
||||
licence_categories: 'Führerscheinklassen',
|
||||
|
||||
@@ -124,8 +124,8 @@ export function processUserFormData(rawData) {
|
||||
}
|
||||
}
|
||||
};
|
||||
console.log('Categories: --------');
|
||||
console.dir(rawData.object.licence);
|
||||
// console.log('Categories: --------');
|
||||
// console.dir(rawData.object.licence);
|
||||
if (
|
||||
rawData.object.password &&
|
||||
rawData.confirm_password &&
|
||||
|
||||
@@ -187,6 +187,37 @@ export const actions = {
|
||||
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();
|
||||
console.log('Server success response:', response);
|
||||
throw redirect(303, `${base}/auth/admin/users`);
|
||||
|
||||
@@ -234,6 +234,41 @@
|
||||
<th>{$t('user.id')}</th>
|
||||
<td>{user.id}</td>
|
||||
</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>
|
||||
<th>{$t('name')}</th>
|
||||
<td>{user.first_name} {user.last_name}</td>
|
||||
@@ -568,6 +603,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap; /* Allows wrapping on small screens */
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.container {
|
||||
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
AwaitingPaymentStatus
|
||||
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
|
||||
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
||||
MailGrantBackendAccessSubject = "Dein Dörpsmobil Hasloh e.V. Zugang"
|
||||
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
||||
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
||||
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
||||
@@ -66,11 +67,13 @@ var Priviliges = struct {
|
||||
Create int8
|
||||
Update int8
|
||||
Delete int8
|
||||
AccessControl int8
|
||||
}{
|
||||
View: 2,
|
||||
Update: 4,
|
||||
Create: 4,
|
||||
Delete: 4,
|
||||
AccessControl: 8,
|
||||
}
|
||||
|
||||
var Roles = struct {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"GoMembership/internal/constants"
|
||||
"GoMembership/internal/utils"
|
||||
"GoMembership/pkg/errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -12,6 +13,55 @@ import (
|
||||
"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) {
|
||||
|
||||
// Expected data from the user
|
||||
|
||||
@@ -141,7 +141,7 @@ func (uc *UserController) DeleteUser(c *gin.Context) {
|
||||
|
||||
type deleteData struct {
|
||||
User struct {
|
||||
ID uint `json:"id"`
|
||||
ID uint `json:"id" binding:"required,numeric"`
|
||||
LastName string `json:"last_name"`
|
||||
} `json:"user"`
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
|
||||
userRouter.POST("/users", userController.RegisterUser)
|
||||
userRouter.GET("/users", userController.GetAllUsers)
|
||||
userRouter.DELETE("/users", userController.DeleteUser)
|
||||
userRouter.PATCH("/users/activate", userController.CreatePasswordHandler)
|
||||
userRouter.GET("/subscriptions", membershipcontroller.GetSubscriptions)
|
||||
userRouter.PUT("/subscriptions", membershipcontroller.UpdateHandler)
|
||||
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, "", "")
|
||||
|
||||
}
|
||||
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 {
|
||||
// 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