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