mirror of
https://github.com/nikdoof/pocket-id.git
synced 2025-12-14 07:12:19 +00:00
feat: add option to skip TLS certificate check and ability to send test email
This commit is contained in:
11
backend/email-templates/test_html.tmpl
Normal file
11
backend/email-templates/test_html.tmpl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{{ define "base" -}}
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="{{ .LogoURL }}" alt="Pocket ID"/>
|
||||||
|
<h1>{{ .AppName }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>This is a test email.</p>
|
||||||
|
</div>
|
||||||
|
{{ end -}}
|
||||||
3
backend/email-templates/test_text.tmpl
Normal file
3
backend/email-templates/test_text.tmpl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{{ define "base" -}}
|
||||||
|
This is a test email.
|
||||||
|
{{ end -}}
|
||||||
@@ -30,7 +30,7 @@ func initRouter(db *gorm.DB, appConfigService *service.AppConfigService) {
|
|||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
templateDir := os.DirFS(common.EnvConfig.EmailTemplatesPath)
|
templateDir := os.DirFS(common.EnvConfig.EmailTemplatesPath)
|
||||||
emailService, err := service.NewEmailService(appConfigService, templateDir)
|
emailService, err := service.NewEmailService(appConfigService, db, templateDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to create email service: %s", err)
|
log.Fatalf("Unable to create email service: %s", err)
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ func initRouter(db *gorm.DB, appConfigService *service.AppConfigService) {
|
|||||||
controller.NewWebauthnController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), webauthnService)
|
controller.NewWebauthnController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), webauthnService)
|
||||||
controller.NewOidcController(apiGroup, jwtAuthMiddleware, fileSizeLimitMiddleware, oidcService, jwtService)
|
controller.NewOidcController(apiGroup, jwtAuthMiddleware, fileSizeLimitMiddleware, oidcService, jwtService)
|
||||||
controller.NewUserController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), userService, appConfigService)
|
controller.NewUserController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), userService, appConfigService)
|
||||||
controller.NewAppConfigController(apiGroup, jwtAuthMiddleware, appConfigService)
|
controller.NewAppConfigController(apiGroup, jwtAuthMiddleware, appConfigService, emailService)
|
||||||
controller.NewAuditLogController(apiGroup, auditLogService, jwtAuthMiddleware)
|
controller.NewAuditLogController(apiGroup, auditLogService, jwtAuthMiddleware)
|
||||||
controller.NewUserGroupController(apiGroup, jwtAuthMiddleware, userGroupService)
|
controller.NewUserGroupController(apiGroup, jwtAuthMiddleware, userGroupService)
|
||||||
controller.NewCustomClaimController(apiGroup, jwtAuthMiddleware, customClaimService)
|
controller.NewCustomClaimController(apiGroup, jwtAuthMiddleware, customClaimService)
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ import (
|
|||||||
func NewAppConfigController(
|
func NewAppConfigController(
|
||||||
group *gin.RouterGroup,
|
group *gin.RouterGroup,
|
||||||
jwtAuthMiddleware *middleware.JwtAuthMiddleware,
|
jwtAuthMiddleware *middleware.JwtAuthMiddleware,
|
||||||
appConfigService *service.AppConfigService) {
|
appConfigService *service.AppConfigService,
|
||||||
|
emailService *service.EmailService,
|
||||||
|
) {
|
||||||
|
|
||||||
acc := &AppConfigController{
|
acc := &AppConfigController{
|
||||||
appConfigService: appConfigService,
|
appConfigService: appConfigService,
|
||||||
|
emailService: emailService,
|
||||||
}
|
}
|
||||||
group.GET("/application-configuration", acc.listAppConfigHandler)
|
group.GET("/application-configuration", acc.listAppConfigHandler)
|
||||||
group.GET("/application-configuration/all", jwtAuthMiddleware.Add(true), acc.listAllAppConfigHandler)
|
group.GET("/application-configuration/all", jwtAuthMiddleware.Add(true), acc.listAllAppConfigHandler)
|
||||||
@@ -29,10 +32,13 @@ func NewAppConfigController(
|
|||||||
group.PUT("/application-configuration/logo", jwtAuthMiddleware.Add(true), acc.updateLogoHandler)
|
group.PUT("/application-configuration/logo", jwtAuthMiddleware.Add(true), acc.updateLogoHandler)
|
||||||
group.PUT("/application-configuration/favicon", jwtAuthMiddleware.Add(true), acc.updateFaviconHandler)
|
group.PUT("/application-configuration/favicon", jwtAuthMiddleware.Add(true), acc.updateFaviconHandler)
|
||||||
group.PUT("/application-configuration/background-image", jwtAuthMiddleware.Add(true), acc.updateBackgroundImageHandler)
|
group.PUT("/application-configuration/background-image", jwtAuthMiddleware.Add(true), acc.updateBackgroundImageHandler)
|
||||||
|
|
||||||
|
group.POST("/application-configuration/test-email", jwtAuthMiddleware.Add(true), acc.testEmailHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppConfigController struct {
|
type AppConfigController struct {
|
||||||
appConfigService *service.AppConfigService
|
appConfigService *service.AppConfigService
|
||||||
|
emailService *service.EmailService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acc *AppConfigController) listAppConfigHandler(c *gin.Context) {
|
func (acc *AppConfigController) listAppConfigHandler(c *gin.Context) {
|
||||||
@@ -175,3 +181,13 @@ func (acc *AppConfigController) updateImage(c *gin.Context, imageName string, ol
|
|||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (acc *AppConfigController) testEmailHandler(c *gin.Context) {
|
||||||
|
err := acc.emailService.SendTestEmail()
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ type AppConfigUpdateDto struct {
|
|||||||
SmtpFrom string `json:"smtpFrom" binding:"omitempty,email"`
|
SmtpFrom string `json:"smtpFrom" binding:"omitempty,email"`
|
||||||
SmtpUser string `json:"smtpUser"`
|
SmtpUser string `json:"smtpUser"`
|
||||||
SmtpPassword string `json:"smtpPassword"`
|
SmtpPassword string `json:"smtpPassword"`
|
||||||
|
SmtpSkipCertVerify string `json:"smtpSkipCertVerify"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ type AppConfig struct {
|
|||||||
LogoLightImageType AppConfigVariable
|
LogoLightImageType AppConfigVariable
|
||||||
LogoDarkImageType AppConfigVariable
|
LogoDarkImageType AppConfigVariable
|
||||||
|
|
||||||
EmailEnabled AppConfigVariable
|
EmailEnabled AppConfigVariable
|
||||||
SmtpHost AppConfigVariable
|
SmtpHost AppConfigVariable
|
||||||
SmtpPort AppConfigVariable
|
SmtpPort AppConfigVariable
|
||||||
SmtpFrom AppConfigVariable
|
SmtpFrom AppConfigVariable
|
||||||
SmtpUser AppConfigVariable
|
SmtpUser AppConfigVariable
|
||||||
SmtpPassword AppConfigVariable
|
SmtpPassword AppConfigVariable
|
||||||
|
SmtpSkipCertVerify AppConfigVariable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ func (u User) WebAuthnCredentialDescriptors() (descriptors []protocol.Credential
|
|||||||
return descriptors
|
return descriptors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u User) FullName() string { return u.FirstName + " " + u.LastName }
|
||||||
|
|
||||||
type OneTimeAccessToken struct {
|
type OneTimeAccessToken struct {
|
||||||
Base
|
Base
|
||||||
Token string
|
Token string
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ var defaultDbConfig = model.AppConfig{
|
|||||||
Key: "smtpPassword",
|
Key: "smtpPassword",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
SmtpSkipCertVerify: model.AppConfigVariable{
|
||||||
|
Key: "smtpSkipCertVerify",
|
||||||
|
Type: "bool",
|
||||||
|
DefaultValue: "false",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AppConfigService) UpdateAppConfig(input dto.AppConfigUpdateDto) ([]model.AppConfigVariable, error) {
|
func (s *AppConfigService) UpdateAppConfig(input dto.AppConfigUpdateDto) ([]model.AppConfigVariable, error) {
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/stonith404/pocket-id/backend/internal/common"
|
||||||
|
"github.com/stonith404/pocket-id/backend/internal/model"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/email"
|
"github.com/stonith404/pocket-id/backend/internal/utils/email"
|
||||||
|
"gorm.io/gorm"
|
||||||
htemplate "html/template"
|
htemplate "html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"mime/quotedprintable"
|
"mime/quotedprintable"
|
||||||
|
"net"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
ttemplate "text/template"
|
ttemplate "text/template"
|
||||||
@@ -17,11 +21,12 @@ import (
|
|||||||
|
|
||||||
type EmailService struct {
|
type EmailService struct {
|
||||||
appConfigService *AppConfigService
|
appConfigService *AppConfigService
|
||||||
|
db *gorm.DB
|
||||||
htmlTemplates map[string]*htemplate.Template
|
htmlTemplates map[string]*htemplate.Template
|
||||||
textTemplates map[string]*ttemplate.Template
|
textTemplates map[string]*ttemplate.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEmailService(appConfigService *AppConfigService, templateDir fs.FS) (*EmailService, error) {
|
func NewEmailService(appConfigService *AppConfigService, db *gorm.DB, templateDir fs.FS) (*EmailService, error) {
|
||||||
htmlTemplates, err := email.PrepareHTMLTemplates(templateDir, emailTemplatesPaths)
|
htmlTemplates, err := email.PrepareHTMLTemplates(templateDir, emailTemplatesPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("prepare html templates: %w", err)
|
return nil, fmt.Errorf("prepare html templates: %w", err)
|
||||||
@@ -34,11 +39,25 @@ func NewEmailService(appConfigService *AppConfigService, templateDir fs.FS) (*Em
|
|||||||
|
|
||||||
return &EmailService{
|
return &EmailService{
|
||||||
appConfigService: appConfigService,
|
appConfigService: appConfigService,
|
||||||
|
db: db,
|
||||||
htmlTemplates: htmlTemplates,
|
htmlTemplates: htmlTemplates,
|
||||||
textTemplates: textTemplates,
|
textTemplates: textTemplates,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *EmailService) SendTestEmail() error {
|
||||||
|
var user model.User
|
||||||
|
if err := srv.db.First(&user).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return SendEmail(srv,
|
||||||
|
email.Address{
|
||||||
|
Email: user.Email,
|
||||||
|
Name: user.FullName(),
|
||||||
|
}, TestTemplate, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.Template[V], tData *V) error {
|
func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.Template[V], tData *V) error {
|
||||||
// Check if SMTP settings are set
|
// Check if SMTP settings are set
|
||||||
if srv.appConfigService.DbConfig.EmailEnabled.Value != "true" {
|
if srv.appConfigService.DbConfig.EmailEnabled.Value != "true" {
|
||||||
@@ -71,26 +90,100 @@ func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.T
|
|||||||
)
|
)
|
||||||
c.Body(body)
|
c.Body(body)
|
||||||
|
|
||||||
// Set up the authentication information.
|
// Set up the TLS configuration
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: srv.appConfigService.DbConfig.SmtpSkipCertVerify.Value == "true",
|
||||||
|
ServerName: srv.appConfigService.DbConfig.SmtpHost.Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the SMTP server
|
||||||
|
port := srv.appConfigService.DbConfig.SmtpPort.Value
|
||||||
|
var client *smtp.Client
|
||||||
|
if port == "465" {
|
||||||
|
client, err = srv.connectToSmtpServerUsingImplicitTLS(
|
||||||
|
srv.appConfigService.DbConfig.SmtpHost.Value+":"+port,
|
||||||
|
tlsConfig,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
client, err = srv.connectToSmtpServerUsingStartTLS(
|
||||||
|
srv.appConfigService.DbConfig.SmtpHost.Value+":"+port,
|
||||||
|
tlsConfig,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
defer client.Quit()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the authentication
|
||||||
auth := smtp.PlainAuth("",
|
auth := smtp.PlainAuth("",
|
||||||
srv.appConfigService.DbConfig.SmtpUser.Value,
|
srv.appConfigService.DbConfig.SmtpUser.Value,
|
||||||
srv.appConfigService.DbConfig.SmtpPassword.Value,
|
srv.appConfigService.DbConfig.SmtpPassword.Value,
|
||||||
srv.appConfigService.DbConfig.SmtpHost.Value,
|
srv.appConfigService.DbConfig.SmtpHost.Value,
|
||||||
)
|
)
|
||||||
|
if err := client.Auth(auth); err != nil {
|
||||||
// Send the email
|
return fmt.Errorf("failed to authenticate SMTP client: %w", err)
|
||||||
err = smtp.SendMail(
|
|
||||||
srv.appConfigService.DbConfig.SmtpHost.Value+":"+srv.appConfigService.DbConfig.SmtpPort.Value,
|
|
||||||
auth,
|
|
||||||
srv.appConfigService.DbConfig.SmtpFrom.Value,
|
|
||||||
[]string{toEmail.Email},
|
|
||||||
[]byte(c.String()),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to send email: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the email
|
||||||
|
if err := srv.sendEmailContent(client, toEmail, c); err != nil {
|
||||||
|
return fmt.Errorf("send email content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *EmailService) connectToSmtpServerUsingImplicitTLS(serverAddr string, tlsConfig *tls.Config) (*smtp.Client, error) {
|
||||||
|
conn, err := tls.Dial("tcp", serverAddr, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := smtp.NewClient(conn, srv.appConfigService.DbConfig.SmtpHost.Value)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("failed to create SMTP client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *EmailService) connectToSmtpServerUsingStartTLS(serverAddr string, tlsConfig *tls.Config) (*smtp.Client, error) {
|
||||||
|
conn, err := net.Dial("tcp", serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := smtp.NewClient(conn, srv.appConfigService.DbConfig.SmtpHost.Value)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("failed to create SMTP client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.StartTLS(tlsConfig); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to start TLS: %w", err)
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *EmailService) sendEmailContent(client *smtp.Client, toEmail email.Address, c *email.Composer) error {
|
||||||
|
if err := client.Mail(srv.appConfigService.DbConfig.SmtpFrom.Value); err != nil {
|
||||||
|
return fmt.Errorf("failed to set sender: %w", err)
|
||||||
|
}
|
||||||
|
if err := client.Rcpt(toEmail.Email); err != nil {
|
||||||
|
return fmt.Errorf("failed to set recipient: %w", err)
|
||||||
|
}
|
||||||
|
w, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start data: %w", err)
|
||||||
|
}
|
||||||
|
_, err = w.Write([]byte(c.String()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write email data: %w", err)
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
return fmt.Errorf("failed to close data writer: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ var NewLoginTemplate = email.Template[NewLoginTemplateData]{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var TestTemplate = email.Template[struct{}]{
|
||||||
|
Path: "test",
|
||||||
|
Title: func(data *email.TemplateData[struct{}]) string {
|
||||||
|
return "Test email"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type NewLoginTemplateData struct {
|
type NewLoginTemplateData struct {
|
||||||
IPAddress string
|
IPAddress string
|
||||||
Country string
|
Country string
|
||||||
@@ -36,4 +43,4 @@ type NewLoginTemplateData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this is list of all template paths used for preloading templates
|
// this is list of all template paths used for preloading templates
|
||||||
var emailTemplatesPaths = []string{NewLoginTemplate.Path}
|
var emailTemplatesPaths = []string{NewLoginTemplate.Path, TestTemplate.Path}
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ func (s *OidcService) GetUserClaimsForClient(userID string, clientID string) (ma
|
|||||||
profileClaims := map[string]interface{}{
|
profileClaims := map[string]interface{}{
|
||||||
"given_name": user.FirstName,
|
"given_name": user.FirstName,
|
||||||
"family_name": user.LastName,
|
"family_name": user.LastName,
|
||||||
"name": user.FirstName + " " + user.LastName,
|
"name": user.FullName(),
|
||||||
"preferred_username": user.Username,
|
"preferred_username": user.Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ export default class AppConfigService extends APIService {
|
|||||||
await this.api.put(`/application-configuration/background-image`, formData);
|
await this.api.put(`/application-configuration/background-image`, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendTestEmail() {
|
||||||
|
await this.api.post('/application-configuration/test-email');
|
||||||
|
}
|
||||||
|
|
||||||
async getVersionInformation() {
|
async getVersionInformation() {
|
||||||
const response = (
|
const response = (
|
||||||
await axios.get('https://api.github.com/repos/stonith404/pocket-id/releases/latest')
|
await axios.get('https://api.github.com/repos/stonith404/pocket-id/releases/latest')
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type AllAppConfig = AppConfig & {
|
|||||||
smtpFrom: string;
|
smtpFrom: string;
|
||||||
smtpUser: string;
|
smtpUser: string;
|
||||||
smtpPassword: string;
|
smtpPassword: string;
|
||||||
|
smtpSkipCertVerify: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppConfigRawResponse = {
|
export type AppConfigRawResponse = {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import CheckboxWithLabel from '$lib/components/checkbox-with-label.svelte';
|
||||||
import FormInput from '$lib/components/form-input.svelte';
|
import FormInput from '$lib/components/form-input.svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import AppConfigService from '$lib/services/app-config-service';
|
||||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||||
import { createForm } from '$lib/utils/form-util';
|
import { createForm } from '$lib/utils/form-util';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
@@ -14,7 +16,9 @@
|
|||||||
callback: (appConfig: Partial<AllAppConfig>) => Promise<void>;
|
callback: (appConfig: Partial<AllAppConfig>) => Promise<void>;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let isLoading = $state(false);
|
const appConfigService = new AppConfigService();
|
||||||
|
|
||||||
|
let isSendingTestEmail = $state(false);
|
||||||
let emailEnabled = $state(appConfig.emailEnabled);
|
let emailEnabled = $state(appConfig.emailEnabled);
|
||||||
|
|
||||||
const updatedAppConfig = {
|
const updatedAppConfig = {
|
||||||
@@ -23,7 +27,8 @@
|
|||||||
smtpPort: appConfig.smtpPort,
|
smtpPort: appConfig.smtpPort,
|
||||||
smtpUser: appConfig.smtpUser,
|
smtpUser: appConfig.smtpUser,
|
||||||
smtpPassword: appConfig.smtpPassword,
|
smtpPassword: appConfig.smtpPassword,
|
||||||
smtpFrom: appConfig.smtpFrom
|
smtpFrom: appConfig.smtpFrom,
|
||||||
|
smtpSkipCertVerify: appConfig.smtpSkipCertVerify
|
||||||
};
|
};
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
@@ -31,7 +36,8 @@
|
|||||||
smtpPort: z.number().min(1),
|
smtpPort: z.number().min(1),
|
||||||
smtpUser: z.string().min(1),
|
smtpUser: z.string().min(1),
|
||||||
smtpPassword: z.string().min(1),
|
smtpPassword: z.string().min(1),
|
||||||
smtpFrom: z.string().email()
|
smtpFrom: z.string().email(),
|
||||||
|
smtpSkipCertVerify: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
||||||
@@ -39,11 +45,10 @@
|
|||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
const data = form.validate();
|
const data = form.validate();
|
||||||
if (!data) return false;
|
if (!data) return false;
|
||||||
isLoading = true;
|
|
||||||
await callback({
|
await callback({
|
||||||
...data,
|
...data,
|
||||||
emailEnabled: true
|
emailEnabled: true
|
||||||
}).finally(() => (isLoading = false));
|
});
|
||||||
toast.success('Email configuration updated successfully');
|
toast.success('Email configuration updated successfully');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -59,6 +64,17 @@
|
|||||||
emailEnabled = true;
|
emailEnabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onTestEmail() {
|
||||||
|
isSendingTestEmail = true;
|
||||||
|
await appConfigService
|
||||||
|
.sendTestEmail()
|
||||||
|
.then(() => toast.success('Test email sent successfully to your Email address.'))
|
||||||
|
.catch(() =>
|
||||||
|
toast.error('Failed to send test email. Check the server logs for more information.')
|
||||||
|
)
|
||||||
|
.finally(() => (isSendingTestEmail = false));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form onsubmit={onSubmit}>
|
<form onsubmit={onSubmit}>
|
||||||
@@ -68,13 +84,23 @@
|
|||||||
<FormInput label="SMTP User" bind:input={$inputs.smtpUser} />
|
<FormInput label="SMTP User" bind:input={$inputs.smtpUser} />
|
||||||
<FormInput label="SMTP Password" type="password" bind:input={$inputs.smtpPassword} />
|
<FormInput label="SMTP Password" type="password" bind:input={$inputs.smtpPassword} />
|
||||||
<FormInput label="SMTP From" bind:input={$inputs.smtpFrom} />
|
<FormInput label="SMTP From" bind:input={$inputs.smtpFrom} />
|
||||||
|
<CheckboxWithLabel
|
||||||
|
id="skip-cert-verify"
|
||||||
|
label="Skip Certificate Verification"
|
||||||
|
description="This can be useful for self-signed certificates."
|
||||||
|
bind:checked={$inputs.smtpSkipCertVerify.value}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 flex justify-end gap-3">
|
<div class="mt-8 flex justify-end gap-3">
|
||||||
{#if emailEnabled}
|
{#if emailEnabled}
|
||||||
<Button variant="secondary" onclick={onDisable}>Disable</Button>
|
<Button variant="secondary" onclick={onDisable}>Disable</Button>
|
||||||
<Button {isLoading} onclick={onSubmit} type="submit">Save</Button>
|
<Button isLoading={isSendingTestEmail} variant="secondary" onclick={onTestEmail}
|
||||||
|
>Send Test Email</Button
|
||||||
|
>
|
||||||
|
|
||||||
|
<Button onclick={onSubmit} type="submit">Save</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<Button {isLoading} onclick={onEnable} type="submit">Enable</Button>
|
<Button onclick={onEnable} type="submit">Enable</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user