Compare commits

...

6 Commits

Author SHA1 Message Date
Elias Schneider
d2b3b7647d release: 0.8.0 2024-10-04 12:11:43 +02:00
Elias Schneider
025378d14e feat: add location based on ip to the audit log 2024-10-04 12:11:10 +02:00
Elias Schneider
e033ba6d45 release: 0.7.1 2024-10-03 22:20:37 +02:00
Elias Schneider
e09562824a fix: initials don't get displayed if Gravatar avatar doesn't exist 2024-10-03 22:20:22 +02:00
Elias Schneider
08f7fd16a9 release: 0.7.0 2024-10-03 11:31:11 +02:00
Elias Schneider
be45eed125 feat!: add ability to set light and dark mode logo 2024-10-03 11:27:31 +02:00
35 changed files with 283 additions and 63 deletions

View File

@@ -23,6 +23,9 @@ jobs:
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Download GeoLite2 City database
run: MAXMIND_LICENSE_KEY=${{ secrets.MAXMIND_LICENSE_KEY }} sh scripts/download-ip-database.sh
- name: Build and push
uses: docker/build-push-action@v4
with:

3
.gitignore vendored
View File

@@ -34,4 +34,5 @@ vite.config.ts.timestamp-*
# Application specific
data
/frontend/tests/.auth
pocket-id-backend
pocket-id-backend
/backend/GeoLite2-City.mmdb

View File

@@ -1 +1 @@
0.6.0
0.8.0

View File

@@ -1,3 +1,28 @@
## [](https://github.com/stonith404/pocket-id/compare/v0.7.1...v) (2024-10-04)
### Features
* add location based on ip to the audit log ([025378d](https://github.com/stonith404/pocket-id/commit/025378d14edd2d72da76e90799a0ccdd42cf672c))
## [](https://github.com/stonith404/pocket-id/compare/v0.7.0...v) (2024-10-03)
### Bug Fixes
* initials don't get displayed if Gravatar avatar doesn't exist ([e095628](https://github.com/stonith404/pocket-id/commit/e09562824a794bc7d240e9d229709d4b389db7d5))
## [](https://github.com/stonith404/pocket-id/compare/v0.6.0...v) (2024-10-03)
### ⚠ BREAKING CHANGES
* add ability to set light and dark mode logo
### Features
* add ability to set light and dark mode logo ([be45eed](https://github.com/stonith404/pocket-id/commit/be45eed125e33e9930572660a034d5f12dc310ce))
## [](https://github.com/stonith404/pocket-id/compare/v0.5.3...v) (2024-10-02)

View File

@@ -31,6 +31,7 @@ COPY --from=frontend-builder /app/frontend/package.json ./frontend/package.json
COPY --from=backend-builder /app/backend/pocket-id-backend ./backend/pocket-id-backend
COPY --from=backend-builder /app/backend/migrations ./backend/migrations
COPY --from=backend-builder /app/backend/GeoLite2-City.mmdb ./backend/GeoLite2-City.mmdb
COPY --from=backend-builder /app/backend/email-templates ./backend/email-templates
COPY --from=backend-builder /app/backend/images ./backend/images

View File

@@ -68,6 +68,10 @@ Required tools:
cd ..
pm2 start pocket-id-backend --name pocket-id-backend
# Optional: Download the GeoLite2 city database.
# If not downloaded the ip location in the audit log will be empty.
MAXMIND_LICENSE_KEY=<your-key> sh scripts/download-ip-database.sh
# Start the frontend
cd ../frontend
npm install
@@ -94,7 +98,6 @@ You may need the following information:
- **Userinfo URL**: `https://<your-domain>/api/oidc/userinfo`
- **Certificate URL**: `https://<your-domain>/.well-known/jwks.json`
- **OIDC Discovery URL**: `https://<your-domain>/.well-known/openid-configuration`
- **PKCE**: `false` as this is not supported yet.
- **Scopes**: At least `openid email`. Optionally you can add `profile` and `groups`.
### Proxy Services with Pocket ID
@@ -132,6 +135,9 @@ docker compose up -d
cd ..
pm2 start pocket-id-backend --name pocket-id-backend
# Optional: Update the GeoLite2 city database
MAXMIND_LICENSE_KEY=<your-key> sh scripts/download-ip-database.sh
# Start the frontend
cd ../frontend
npm install

View File

@@ -9,9 +9,15 @@
<div class="content">
<h2>New Sign-In Detected</h2>
<div class="grid">
{{ if and .Data.City .Data.Country }}
<div>
<p class="label">Approximate Location</p>
<p>{{ .Data.City }}, {{ .Data.Country }}</p>
</div>
{{ end }}
<div>
<p class="label">IP Address</p>
<p>{{ .Data.IPAddress}}</p>
<p>{{ .Data.IPAddress }}</p>
</div>
<div>
<p class="label">Device</p>
@@ -19,7 +25,7 @@
</div>
<div>
<p class="label">Sign-In Time</p>
<p>{{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC"}}</p>
<p>{{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC" }}</p>
</div>
</div>
<p class="message">
@@ -27,4 +33,4 @@
safely ignore this message. If not, please review your account and security settings.
</p>
</div>
{{ end -}}
{{ end -}}

View File

@@ -2,6 +2,9 @@
New Sign-In Detected
====================
{{ if and .Data.City .Data.Country }}
Approximate Location: {{ .Data.City }}, {{ .Data.Country }}
{{ end }}
IP Address: {{ .Data.IPAddress }}
Device: {{ .Data.Device }}
Time: {{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC"}}

View File

@@ -15,6 +15,7 @@ require (
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/mileusna/useragent v1.3.4
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1
golang.org/x/crypto v0.26.0
golang.org/x/time v0.6.0
gorm.io/driver/sqlite v1.5.6

View File

@@ -90,6 +90,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1 h1:UihPOz+oIJ5X0JsO7wEkL50fheCODsoZ9r86mJWfNMc=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1/go.mod h1:vPpFrres6g9B5+meBwAd9xnp335KFcLEFW7EqJxBHy0=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" id="a" viewBox="0 0 1015 1015">
<path fill="white" d="M506.6,0c209.52,0,379.98,170.45,379.98,379.96,0,82.33-25.9,160.68-74.91,226.54-48.04,64.59-113.78,111.51-190.13,135.71l-21.1,6.7-50.29-248.04,13.91-6.73c45.41-21.95,74.76-68.71,74.76-119.11,0-72.91-59.31-132.23-132.21-132.23s-132.23,59.32-132.23,132.23c0,50.4,29.36,97.16,74.77,119.11l13.65,6.61-81.01,499.24h-226.36V0h351.18Z"/>
</svg>

After

Width:  |  Height:  |  Size: 434 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" id="a" viewBox="0 0 1015 1015">
<path fill="black" d="M506.6,0c209.52,0,379.98,170.45,379.98,379.96,0,82.33-25.9,160.68-74.91,226.54-48.04,64.59-113.78,111.51-190.13,135.71l-21.1,6.7-50.29-248.04,13.91-6.73c45.41-21.95,74.76-68.71,74.76-119.11,0-72.91-59.31-132.23-132.21-132.23s-132.23,59.32-132.23,132.23c0,50.4,29.36,97.16,74.77,119.11l13.65,6.61-81.01,499.24h-226.36V0h351.18Z"/>
</svg>

After

Width:  |  Height:  |  Size: 434 B

View File

@@ -5,24 +5,53 @@ import (
"github.com/stonith404/pocket-id/backend/internal/utils"
"log"
"os"
"strings"
)
// initApplicationImages copies the images from the images directory to the application-images directory
func initApplicationImages() {
dirPath := common.EnvConfig.UploadPath + "/application-images"
files, err := os.ReadDir(dirPath)
sourceFiles, err := os.ReadDir("./images")
if err != nil && !os.IsNotExist(err) {
log.Fatalf("Error reading directory: %v", err)
}
// Skip if files already exist
if len(files) > 1 {
return
destinationFiles, err := os.ReadDir(dirPath)
if err != nil && !os.IsNotExist(err) {
log.Fatalf("Error reading directory: %v", err)
}
// Copy files from source to destination
err = utils.CopyDirectory("./images", dirPath)
if err != nil {
log.Fatalf("Error copying directory: %v", err)
// Copy images from the images directory to the application-images directory if they don't already exist
for _, sourceFile := range sourceFiles {
if sourceFile.IsDir() || imageAlreadyExists(sourceFile.Name(), destinationFiles) {
continue
}
srcFilePath := "./images/" + sourceFile.Name()
destFilePath := dirPath + "/" + sourceFile.Name()
err := utils.CopyFile(srcFilePath, destFilePath)
if err != nil {
log.Fatalf("Error copying file: %v", err)
}
}
}
func imageAlreadyExists(fileName string, destinationFiles []os.DirEntry) bool {
for _, destinationFile := range destinationFiles {
sourceFileWithoutExtension := getImageNameWithoutExtension(fileName)
destinationFileWithoutExtension := getImageNameWithoutExtension(destinationFile.Name())
if sourceFileWithoutExtension == destinationFileWithoutExtension {
return true
}
}
return false
}
func getImageNameWithoutExtension(fileName string) string {
splitted := strings.Split(fileName, ".")
return strings.Join(splitted[:len(splitted)-1], ".")
}

View File

@@ -91,8 +91,20 @@ func (acc *AppConfigController) updateAppConfigHandler(c *gin.Context) {
}
func (acc *AppConfigController) getLogoHandler(c *gin.Context) {
imageType := acc.appConfigService.DbConfig.LogoImageType.Value
acc.getImage(c, "logo", imageType)
lightLogo := c.DefaultQuery("light", "true") == "true"
var imageName string
var imageType string
if lightLogo {
imageName = "logoLight"
imageType = acc.appConfigService.DbConfig.LogoLightImageType.Value
} else {
imageName = "logoDark"
imageType = acc.appConfigService.DbConfig.LogoDarkImageType.Value
}
acc.getImage(c, imageName, imageType)
}
func (acc *AppConfigController) getFaviconHandler(c *gin.Context) {
@@ -105,8 +117,20 @@ func (acc *AppConfigController) getBackgroundImageHandler(c *gin.Context) {
}
func (acc *AppConfigController) updateLogoHandler(c *gin.Context) {
imageType := acc.appConfigService.DbConfig.LogoImageType.Value
acc.updateImage(c, "logo", imageType)
lightLogo := c.DefaultQuery("light", "true") == "true"
var imageName string
var imageType string
if lightLogo {
imageName = "logoLight"
imageType = acc.appConfigService.DbConfig.LogoLightImageType.Value
} else {
imageName = "logoDark"
imageType = acc.appConfigService.DbConfig.LogoDarkImageType.Value
}
acc.updateImage(c, imageName, imageType)
}
func (acc *AppConfigController) updateFaviconHandler(c *gin.Context) {

View File

@@ -11,6 +11,8 @@ type AuditLogDto struct {
Event model.AuditLogEvent `json:"event"`
IpAddress string `json:"ipAddress"`
Country string `json:"country"`
City string `json:"city"`
Device string `json:"device"`
UserID string `json:"userID"`
Data model.AuditLogData `json:"data"`

View File

@@ -11,7 +11,8 @@ type AppConfigVariable struct {
type AppConfig struct {
AppName AppConfigVariable
BackgroundImageType AppConfigVariable
LogoImageType AppConfigVariable
LogoLightImageType AppConfigVariable
LogoDarkImageType AppConfigVariable
SessionDuration AppConfigVariable
EmailEnabled AppConfigVariable

View File

@@ -11,6 +11,8 @@ type AuditLog struct {
Event AuditLogEvent
IpAddress string
Country string
City string
UserAgent string
UserID string
Data AuditLogData

View File

@@ -47,8 +47,14 @@ var defaultDbConfig = model.AppConfig{
IsInternal: true,
Value: "jpg",
},
LogoImageType: model.AppConfigVariable{
Key: "logoImageType",
LogoLightImageType: model.AppConfigVariable{
Key: "logoLightImageType",
Type: "string",
IsInternal: true,
Value: "svg",
},
LogoDarkImageType: model.AppConfigVariable{
Key: "logoDarkImageType",
Type: "string",
IsInternal: true,
Value: "svg",

View File

@@ -2,11 +2,13 @@ package service
import (
userAgentParser "github.com/mileusna/useragent"
"github.com/oschwald/maxminddb-golang/v2"
"github.com/stonith404/pocket-id/backend/internal/model"
"github.com/stonith404/pocket-id/backend/internal/utils"
"github.com/stonith404/pocket-id/backend/internal/utils/email"
"gorm.io/gorm"
"log"
"net/netip"
)
type AuditLogService struct {
@@ -21,9 +23,16 @@ func NewAuditLogService(db *gorm.DB, appConfigService *AppConfigService, emailSe
// Create creates a new audit log entry in the database
func (s *AuditLogService) Create(event model.AuditLogEvent, ipAddress, userAgent, userID string, data model.AuditLogData) model.AuditLog {
country, city, err := s.GetIpLocation(ipAddress)
if err != nil {
log.Printf("Failed to get IP location: %v\n", err)
}
auditLog := model.AuditLog{
Event: event,
IpAddress: ipAddress,
Country: country,
City: city,
UserAgent: userAgent,
UserID: userID,
Data: data,
@@ -61,6 +70,8 @@ func (s *AuditLogService) CreateNewSignInWithEmail(ipAddress, userAgent, userID
Email: user.Email,
}, NewLoginTemplate, &NewLoginTemplateData{
IPAddress: ipAddress,
Country: createdAuditLog.Country,
City: createdAuditLog.City,
Device: s.DeviceStringFromUserAgent(userAgent),
DateTime: createdAuditLog.CreatedAt.UTC(),
})
@@ -86,3 +97,29 @@ func (s *AuditLogService) DeviceStringFromUserAgent(userAgent string) string {
ua := userAgentParser.Parse(userAgent)
return ua.Name + " on " + ua.OS + " " + ua.OSVersion
}
func (s *AuditLogService) GetIpLocation(ipAddress string) (country, city string, err error) {
db, err := maxminddb.Open("GeoLite2-City.mmdb")
if err != nil {
return "", "", err
}
defer db.Close()
addr := netip.MustParseAddr(ipAddress)
var record struct {
City struct {
Names map[string]string `maxminddb:"names"`
} `maxminddb:"city"`
Country struct {
Names map[string]string `maxminddb:"names"`
} `maxminddb:"country"`
}
err = db.Lookup(addr).Decode(&record)
if err != nil {
return "", "", err
}
return record.Country.Names["en"], record.City.Names["en"], nil
}

View File

@@ -29,6 +29,8 @@ var NewLoginTemplate = email.Template[NewLoginTemplateData]{
type NewLoginTemplateData struct {
IPAddress string
Country string
City string
Device string
DateTime time.Time
}

View File

@@ -38,7 +38,7 @@ func CopyDirectory(srcDir, destDir string) error {
srcFilePath := filepath.Join(srcDir, file.Name())
destFilePath := filepath.Join(destDir, file.Name())
err := copyFile(srcFilePath, destFilePath)
err := CopyFile(srcFilePath, destFilePath)
if err != nil {
return err
}
@@ -47,7 +47,7 @@ func CopyDirectory(srcDir, destDir string) error {
return nil
}
func copyFile(srcFilePath, destFilePath string) error {
func CopyFile(srcFilePath, destFilePath string) error {
srcFile, err := os.Open(srcFilePath)
if err != nil {
return err

View File

@@ -0,0 +1,2 @@
ALTER TABLE audit_logs DROP COLUMN country;
ALTER TABLE audit_logs DROP COLUMN city;

View File

@@ -0,0 +1,2 @@
ALTER TABLE audit_logs ADD COLUMN country TEXT;
ALTER TABLE audit_logs ADD COLUMN city TEXT;

View File

@@ -97,16 +97,4 @@
font-weight: 700;
src: url('/fonts/PlayfairDisplay-Bold.woff') format('woff');
}
}
@layer components {
.application-images-grid {
@apply flex flex-wrap justify-between gap-x-5 gap-y-8;
}
@media (max-width: 1127px) {
.application-images-grid {
justify-content: flex-start;
@apply gap-x-20;
}
}
}
}

View File

@@ -15,7 +15,7 @@
let gravatarURL: string | undefined = $state();
if ($userStore) {
createSHA256hash($userStore.email).then((email) => {
gravatarURL = `https://www.gravatar.com/avatar/${email}`;
gravatarURL = `https://www.gravatar.com/avatar/${email}?d=404`;
});
}

View File

@@ -1 +1,10 @@
<img class={$$restProps.class} src="/api/application-configuration/logo" alt="Logo" />
<script lang="ts">
import { mode } from 'mode-watcher';
import type { HTMLAttributes } from 'svelte/elements';
let { ...props }: HTMLAttributes<HTMLImageElement> = $props();
const isDarkMode = $derived($mode === 'dark');
</script>
<img {...props} src="/api/application-configuration/logo?light={!isDarkMode}" alt="Logo" />

View File

@@ -1,7 +1,4 @@
import type {
AllAppConfig,
AppConfigRawResponse
} from '$lib/types/application-configuration';
import type { AllAppConfig, AppConfigRawResponse } from '$lib/types/application-configuration';
import APIService from './api-service';
export default class AppConfigService extends APIService {
@@ -33,11 +30,13 @@ export default class AppConfigService extends APIService {
await this.api.put(`/application-configuration/favicon`, formData);
}
async updateLogo(logo: File) {
async updateLogo(logo: File, light = true) {
const formData = new FormData();
formData.append('file', logo!);
await this.api.put(`/application-configuration/logo`, formData);
await this.api.put(`/application-configuration/logo`, formData, {
params: { light }
});
}
async updateBackgroundImage(backgroundImage: File) {

View File

@@ -2,6 +2,8 @@ export type AuditLog = {
id: string;
event: string;
ipAddress: string;
country?: string;
city?: string;
device: string;
createdAt: string;
data: any;

View File

@@ -28,17 +28,19 @@
}
async function updateImages(
logo: File | null,
logoLight: File | null,
logoDark: File | null,
backgroundImage: File | null,
favicon: File | null
) {
const faviconPromise = favicon ? appConfigService.updateFavicon(favicon) : Promise.resolve();
const logoPromise = logo ? appConfigService.updateLogo(logo) : Promise.resolve();
const lightLogoPromise = logoLight ? appConfigService.updateLogo(logoLight, true) : Promise.resolve();
const darkLogoPromise = logoDark ? appConfigService.updateLogo(logoDark, false) : Promise.resolve();
const backgroundImagePromise = backgroundImage
? appConfigService.updateBackgroundImage(backgroundImage)
: Promise.resolve();
await Promise.all([logoPromise, backgroundImagePromise, faviconPromise])
await Promise.all([lightLogoPromise, darkLogoPromise, backgroundImagePromise, faviconPromise])
.then(() => toast.success('Images updated successfully'))
.catch(axiosErrorToast);
}

View File

@@ -11,6 +11,7 @@
image = $bindable(),
imageURL,
accept = 'image/png, image/jpeg, image/svg+xml',
forceColorScheme,
...restProps
}: HTMLAttributes<HTMLDivElement> & {
id: string;
@@ -18,6 +19,7 @@
label: string;
image: File | null;
imageURL: string;
forceColorScheme?: 'light' | 'dark';
accept?: string;
} = $props();
@@ -37,10 +39,16 @@
}
</script>
<div {...restProps}>
<Label for={id}>{label}</Label>
<div class="flex flex-col items-start md:flex-row md:items-center" {...restProps}>
<Label class="w-52" for={id}>{label}</Label>
<FileInput {id} variant="secondary" {accept} onchange={onImageChange}>
<div class="bg-muted group relative flex items-center rounded">
<div
class="{forceColorScheme === 'light'
? 'bg-[#F1F1F5]'
: forceColorScheme === 'dark'
? 'bg-[#27272A]'
: 'bg-muted'} group relative flex items-center rounded"
>
<img
class={cn(
'h-full w-full rounded object-cover p-3 transition-opacity duration-200 group-hover:opacity-10',

View File

@@ -5,15 +5,21 @@
let {
callback
}: {
callback: (logo: File | null, backgroundImage: File | null, favicon: File | null) => void;
callback: (
logoLight: File | null,
logoDark: File | null,
backgroundImage: File | null,
favicon: File | null
) => void;
} = $props();
let logo = $state<File | null>(null);
let logoLight = $state<File | null>(null);
let logoDark = $state<File | null>(null);
let backgroundImage = $state<File | null>(null);
let favicon = $state<File | null>(null);
</script>
<div class="application-images-grid">
<div class="flex flex-col gap-8">
<ApplicationImage
id="favicon"
imageClass="h-14 w-14 p-2"
@@ -23,15 +29,23 @@
accept="image/x-icon"
/>
<ApplicationImage
id="logo"
id="logo-light"
imageClass="h-32 w-32"
label="Logo"
bind:image={logo}
imageURL="/api/application-configuration/logo"
label="Light Mode Logo"
bind:image={logoLight}
imageURL="/api/application-configuration/logo?light=true"
forceColorScheme="light"
/>
<ApplicationImage
id="logo-dark"
imageClass="h-32 w-32"
label="Dark Mode Logo"
bind:image={logoDark}
imageURL="/api/application-configuration/logo?light=false"
forceColorScheme="dark"
/>
<ApplicationImage
id="background-image"
class="basis-full lg:basis-auto"
imageClass="h-[350px] max-w-[500px]"
label="Background Image"
bind:image={backgroundImage}
@@ -39,5 +53,7 @@
/>
</div>
<div class="flex justify-end">
<Button class="mt-5" onclick={() => callback(logo, backgroundImage, favicon)}>Save</Button>
<Button class="mt-5" onclick={() => callback(logoLight, logoDark, backgroundImage, favicon)}
>Save</Button
>
</div>

View File

@@ -27,7 +27,6 @@
'Token URL': `https://${$page.url.hostname}/api/oidc/token`,
'Userinfo URL': `https://${$page.url.hostname}/api/oidc/userinfo`,
'Certificate URL': `https://${$page.url.hostname}/.well-known/jwks.json`,
PKCE: 'Disabled'
};
async function updateClient(updatedClient: OidcClientCreateWithLogo) {

View File

@@ -30,6 +30,7 @@
<Table.Row>
<Table.Head>Time</Table.Head>
<Table.Head>Event</Table.Head>
<Table.Head>Approximate Location</Table.Head>
<Table.Head>IP Address</Table.Head>
<Table.Head>Device</Table.Head>
<Table.Head>Client</Table.Head>
@@ -47,6 +48,7 @@
<Table.Cell>
<Badge variant="outline">{toFriendlyEventString(auditLog.event)}</Badge>
</Table.Cell>
<Table.Cell>{auditLog.city && auditLog.country ? `${auditLog.city}, ${auditLog.country}` : 'Unknown'}</Table.Cell>
<Table.Cell>{auditLog.ipAddress}</Table.Cell>
<Table.Cell>{auditLog.device}</Table.Cell>
<Table.Cell>{auditLog.data.clientName}</Table.Cell>

View File

@@ -52,7 +52,8 @@ test('Update application images', async ({ page }) => {
await page.goto('/settings/admin/application-configuration');
await page.getByLabel('Favicon').setInputFiles('tests/assets/w3-schools-favicon.ico');
await page.getByLabel('Logo').setInputFiles('tests/assets/pingvin-share-logo.png');
await page.getByLabel('Light Mode Logo').setInputFiles('tests/assets/pingvin-share-logo.png');
await page.getByLabel('Dark Mode Logo').setInputFiles('tests/assets/nextcloud-logo.png');
await page.getByLabel('Background Image').setInputFiles('tests/assets/clouds.jpg');
await page.getByRole('button', { name: 'Save' }).nth(1).click();
@@ -62,9 +63,11 @@ test('Update application images', async ({ page }) => {
.get('/api/application-configuration/favicon')
.then((res) => expect.soft(res.status()).toBe(200));
await page.request
.get('/api/application-configuration/logo')
.get('/api/application-configuration/logo?light=true')
.then((res) => expect.soft(res.status()).toBe(200));
await page.request
.get('/api/application-configuration/logo?light=false')
.then((res) => expect.soft(res.status()).toBe(200));
await page.request
.get('/api/application-configuration/background-image')
.then((res) => expect.soft(res.status()).toBe(200));

View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Check if the license key environment variable is set
if [ -z "$MAXMIND_LICENSE_KEY" ]; then
echo "Error: MAXMIND_LICENSE_KEY environment variable is not set."
echo "Please set it using 'export MAXMIND_LICENSE_KEY=your_license_key' and try again."
exit 1
fi
echo $MAXMIND_LICENSE_KEY
# GeoLite2 City Database URL
URL="https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz"
# Download directory
DOWNLOAD_DIR="./geolite2_db"
TARGET_PATH=./backend/GeoLite2-City.mmdb
mkdir -p $DOWNLOAD_DIR
# Download the database
echo "Downloading GeoLite2 City database..."
curl -L -o "$DOWNLOAD_DIR/GeoLite2-City.tar.gz" "$URL"
# Extract the downloaded file
echo "Extracting GeoLite2 City database..."
tar -xzf "$DOWNLOAD_DIR/GeoLite2-City.tar.gz" -C $DOWNLOAD_DIR --strip-components=1
mv "$DOWNLOAD_DIR/GeoLite2-City.mmdb" $TARGET_PATH
# Clean up
rm -rf "$DOWNLOAD_DIR"
echo "GeoLite2 City database downloaded and extracted to $TARGET_PATH"