mirror of
https://github.com/nikdoof/pocket-id.git
synced 2025-12-13 14:52:18 +00:00
feat: add option to change session duration
This commit is contained in:
@@ -36,6 +36,11 @@ func NewDefaultDbConfig() model.ApplicationConfiguration {
|
||||
IsPublic: true,
|
||||
Value: "Pocket ID",
|
||||
},
|
||||
SessionDuration: model.ApplicationConfigurationVariable{
|
||||
Key: "sessionDuration",
|
||||
Type: "number",
|
||||
Value: "60",
|
||||
},
|
||||
BackgroundImageType: model.ApplicationConfigurationVariable{
|
||||
Key: "backgroundImageType",
|
||||
Type: "string",
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -73,10 +74,11 @@ func GenerateIDToken(user model.User, clientID string, scope string, nonce strin
|
||||
|
||||
// GenerateAccessToken generates an access token for the given user.
|
||||
func GenerateAccessToken(user model.User) (tokenString string, err error) {
|
||||
sessionDurationInMinutes, _ := strconv.Atoi(DbConfig.SessionDuration.Value)
|
||||
claim := accessTokenJWTClaims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: user.ID,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(sessionDurationInMinutes) * time.Minute)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Audience: jwt.ClaimStrings{utils.GetHostFromURL(EnvConfig.AppURL)},
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
func RegisterConfigurationRoutes(group *gin.RouterGroup) {
|
||||
group.GET("/application-configuration", listApplicationConfigurationHandler)
|
||||
group.GET("/application-configuration/all", middleware.JWTAuth(true), listAllApplicationConfigurationHandler)
|
||||
group.PUT("/application-configuration", updateApplicationConfigurationHandler)
|
||||
|
||||
group.GET("/application-configuration/logo", getLogoHandler)
|
||||
@@ -27,24 +28,11 @@ func RegisterConfigurationRoutes(group *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func listApplicationConfigurationHandler(c *gin.Context) {
|
||||
// Return also the private configuration variables if the user is admin and showAll is true
|
||||
showAll := c.GetBool("userIsAdmin") && c.DefaultQuery("showAll", "false") == "true"
|
||||
listApplicationConfiguration(c, false)
|
||||
}
|
||||
|
||||
var configuration []model.ApplicationConfigurationVariable
|
||||
var err error
|
||||
|
||||
if showAll {
|
||||
err = common.DB.Find(&configuration).Error
|
||||
} else {
|
||||
err = common.DB.Find(&configuration, "is_public = true").Error
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.UnknownHandlerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, configuration)
|
||||
func listAllApplicationConfigurationHandler(c *gin.Context) {
|
||||
listApplicationConfiguration(c, true)
|
||||
}
|
||||
|
||||
func updateApplicationConfigurationHandler(c *gin.Context) {
|
||||
@@ -188,3 +176,21 @@ func updateImage(c *gin.Context, imageName string, oldImageType string) {
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func listApplicationConfiguration(c *gin.Context, showAll bool) {
|
||||
var configuration []model.ApplicationConfigurationVariable
|
||||
var err error
|
||||
|
||||
if showAll {
|
||||
err = common.DB.Find(&configuration).Error
|
||||
} else {
|
||||
err = common.DB.Find(&configuration, "is_public = true").Error
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.UnknownHandlerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, configuration)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type ApplicationConfiguration struct {
|
||||
AppName ApplicationConfigurationVariable
|
||||
BackgroundImageType ApplicationConfigurationVariable
|
||||
LogoImageType ApplicationConfigurationVariable
|
||||
SessionDuration ApplicationConfigurationVariable
|
||||
}
|
||||
|
||||
type ApplicationConfigurationUpdateDto struct {
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
let {
|
||||
input = $bindable(),
|
||||
label,
|
||||
description,
|
||||
children
|
||||
}: {
|
||||
input: FormInput<string | boolean | number>;
|
||||
label: string;
|
||||
description?: string;
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
|
||||
@@ -18,13 +20,18 @@
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Label for={id}>{label}</Label>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<Input {id} bind:value={input.value} />
|
||||
{/if}
|
||||
{#if input.error}
|
||||
<p class="text-sm text-red-500">{input.error}</p>
|
||||
<Label class="mb-0" for={id}>{label}</Label>
|
||||
{#if description}
|
||||
<p class="text-muted-foreground text-xs mt-1">{description}</p>
|
||||
{/if}
|
||||
<div class="mt-2">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<Input {id} bind:value={input.value} />
|
||||
{/if}
|
||||
{#if input.error}
|
||||
<p class="text-sm text-red-500">{input.error}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import WebAuthnService from '$lib/services/webauthn-service';
|
||||
@@ -12,8 +11,8 @@
|
||||
($userStore!.firstName.charAt(0) + $userStore!.lastName?.charAt(0)).toUpperCase()
|
||||
);
|
||||
|
||||
function logout() {
|
||||
webauthnService.logout();
|
||||
async function logout() {
|
||||
await webauthnService.logout();
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
@@ -31,7 +30,7 @@
|
||||
{$userStore?.firstName}
|
||||
{$userStore?.lastName}
|
||||
</p>
|
||||
<p class="text-xs leading-none text-muted-foreground">{$userStore?.email}</p>
|
||||
<p class="text-muted-foreground text-xs leading-none">{$userStore?.email}</p>
|
||||
</div>
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
@@ -6,12 +6,12 @@ import APIService from './api-service';
|
||||
|
||||
export default class ApplicationConfigurationService extends APIService {
|
||||
async list(showAll = false) {
|
||||
const { data } = await this.api.get<ApplicationConfigurationRawResponse>(
|
||||
'/application-configuration',
|
||||
{
|
||||
params: { showAll }
|
||||
}
|
||||
);
|
||||
let url = '/application-configuration';
|
||||
if (showAll) {
|
||||
url += '/all';
|
||||
}
|
||||
|
||||
const { data } = await this.api.get<ApplicationConfigurationRawResponse>(url);
|
||||
|
||||
const applicationConfiguration: Partial<AllApplicationConfiguration> = {};
|
||||
data.forEach(({ key, value }) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
export type AllApplicationConfiguration = {
|
||||
appName: string;
|
||||
sessionDuration: string;
|
||||
};
|
||||
|
||||
export type ApplicationConfiguration = AllApplicationConfiguration;
|
||||
|
||||
@@ -5,6 +5,6 @@ export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const applicationConfigurationService = new ApplicationConfigurationService(
|
||||
cookies.get('access_token')
|
||||
);
|
||||
const applicationConfiguration = await applicationConfigurationService.list();
|
||||
const applicationConfiguration = await applicationConfigurationService.list(true);
|
||||
return { applicationConfiguration };
|
||||
};
|
||||
|
||||
@@ -16,11 +16,21 @@
|
||||
let isLoading = $state(false);
|
||||
|
||||
const updatedApplicationConfiguration: AllApplicationConfiguration = {
|
||||
appName: applicationConfiguration.appName
|
||||
appName: applicationConfiguration.appName,
|
||||
sessionDuration: applicationConfiguration.sessionDuration
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
appName: z.string().min(2).max(30)
|
||||
appName: z.string().min(2).max(30),
|
||||
sessionDuration: z.string().refine(
|
||||
(val) => {
|
||||
const num = Number(val);
|
||||
return Number.isInteger(num) && num >= 1 && num <= 43200;
|
||||
},
|
||||
{
|
||||
message: 'Session duration must be between 1 and 43200 minutes'
|
||||
}
|
||||
)
|
||||
});
|
||||
type FormSchema = typeof formSchema;
|
||||
|
||||
@@ -35,10 +45,14 @@
|
||||
</script>
|
||||
|
||||
<form onsubmit={onSubmit}>
|
||||
<div class="flex gap-3">
|
||||
<div class="w-full">
|
||||
<FormInput label="Application Name" bind:input={$inputs.appName} />
|
||||
</div>
|
||||
<div class="flex flex-col gap-5">
|
||||
<FormInput label="Application Name" bind:input={$inputs.appName} />
|
||||
|
||||
<FormInput
|
||||
label="Session Duration"
|
||||
description="The duration of a session in minutes before the user has to sign in again."
|
||||
bind:input={$inputs.sessionDuration}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end">
|
||||
<Button {isLoading} type="submit">Save</Button>
|
||||
|
||||
@@ -7,6 +7,7 @@ test('Update general configuration', async ({ page }) => {
|
||||
await page.goto('/settings/admin/application-configuration');
|
||||
|
||||
await page.getByLabel('Name').fill('Updated Name');
|
||||
await page.getByLabel('Session Duration').fill('30');
|
||||
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||
|
||||
await expect(page.getByTestId('application-name')).toHaveText('Updated Name');
|
||||
|
||||
Reference in New Issue
Block a user