mirror of
https://github.com/nikdoof/pocket-id.git
synced 2025-12-14 15:22:18 +00:00
feat: add email_verified claim
This commit is contained in:
@@ -37,7 +37,7 @@ func (wkc *WellKnownController) openIDConfigurationHandler(c *gin.Context) {
|
|||||||
"userinfo_endpoint": appUrl + "/api/oidc/userinfo",
|
"userinfo_endpoint": appUrl + "/api/oidc/userinfo",
|
||||||
"jwks_uri": appUrl + "/.well-known/jwks.json",
|
"jwks_uri": appUrl + "/.well-known/jwks.json",
|
||||||
"scopes_supported": []string{"openid", "profile", "email"},
|
"scopes_supported": []string{"openid", "profile", "email"},
|
||||||
"claims_supported": []string{"sub", "given_name", "family_name", "name", "email", "preferred_username"},
|
"claims_supported": []string{"sub", "given_name", "family_name", "name", "email", "email_verified", "preferred_username"},
|
||||||
"response_types_supported": []string{"code", "id_token"},
|
"response_types_supported": []string{"code", "id_token"},
|
||||||
"subject_types_supported": []string{"public"},
|
"subject_types_supported": []string{"public"},
|
||||||
"id_token_signing_alg_values_supported": []string{"RS256"},
|
"id_token_signing_alg_values_supported": []string{"RS256"},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type AppConfigVariableDto struct {
|
|||||||
type AppConfigUpdateDto struct {
|
type AppConfigUpdateDto struct {
|
||||||
AppName string `json:"appName" binding:"required,min=1,max=30"`
|
AppName string `json:"appName" binding:"required,min=1,max=30"`
|
||||||
SessionDuration string `json:"sessionDuration" binding:"required"`
|
SessionDuration string `json:"sessionDuration" binding:"required"`
|
||||||
|
EmailsVerified string `json:"emailsVerified" binding:"required"`
|
||||||
EmailEnabled string `json:"emailEnabled" binding:"required"`
|
EmailEnabled string `json:"emailEnabled" binding:"required"`
|
||||||
SmtHost string `json:"smtpHost"`
|
SmtHost string `json:"smtpHost"`
|
||||||
SmtpPort string `json:"smtpPort"`
|
SmtpPort string `json:"smtpPort"`
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type AppConfig struct {
|
|||||||
LogoLightImageType AppConfigVariable
|
LogoLightImageType AppConfigVariable
|
||||||
LogoDarkImageType AppConfigVariable
|
LogoDarkImageType AppConfigVariable
|
||||||
SessionDuration AppConfigVariable
|
SessionDuration AppConfigVariable
|
||||||
|
EmailsVerified AppConfigVariable
|
||||||
|
|
||||||
EmailEnabled AppConfigVariable
|
EmailEnabled AppConfigVariable
|
||||||
SmtpHost AppConfigVariable
|
SmtpHost AppConfigVariable
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ var defaultDbConfig = model.AppConfig{
|
|||||||
Type: "number",
|
Type: "number",
|
||||||
Value: "60",
|
Value: "60",
|
||||||
},
|
},
|
||||||
|
EmailsVerified: model.AppConfigVariable{
|
||||||
|
Key: "emailsVerified",
|
||||||
|
Type: "bool",
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
BackgroundImageType: model.AppConfigVariable{
|
BackgroundImageType: model.AppConfigVariable{
|
||||||
Key: "backgroundImageType",
|
Key: "backgroundImageType",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ func (s *OidcService) GetUserClaimsForClient(userID string, clientID string) (ma
|
|||||||
|
|
||||||
if strings.Contains(scope, "email") {
|
if strings.Contains(scope, "email") {
|
||||||
claims["email"] = user.Email
|
claims["email"] = user.Email
|
||||||
|
claims["email_verified"] = s.appConfigService.DbConfig.EmailsVerified.Value == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(scope, "groups") {
|
if strings.Contains(scope, "groups") {
|
||||||
|
|||||||
@@ -14,14 +14,19 @@ export default class AppConfigService extends APIService {
|
|||||||
|
|
||||||
const appConfig: Partial<AllAppConfig> = {};
|
const appConfig: Partial<AllAppConfig> = {};
|
||||||
data.forEach(({ key, value }) => {
|
data.forEach(({ key, value }) => {
|
||||||
(appConfig as any)[key] = value;
|
(appConfig as any)[key] = this.parseValue(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return appConfig as AllAppConfig;
|
return appConfig as AllAppConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(appConfig: AllAppConfig) {
|
async update(appConfig: AllAppConfig) {
|
||||||
const res = await this.api.put('/application-configuration', appConfig);
|
// Convert all values to string
|
||||||
|
const appConfigConvertedToString = {};
|
||||||
|
for (const key in appConfig) {
|
||||||
|
(appConfigConvertedToString as any)[key] = (appConfig as any)[key].toString();
|
||||||
|
}
|
||||||
|
const res = await this.api.put('/application-configuration', appConfigConvertedToString);
|
||||||
return res.data as AllAppConfig;
|
return res.data as AllAppConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,4 +67,16 @@ export default class AppConfigService extends APIService {
|
|||||||
currentVersion
|
currentVersion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseValue(value: string) {
|
||||||
|
if (value === 'true') {
|
||||||
|
return true;
|
||||||
|
} else if (value === 'false') {
|
||||||
|
return false;
|
||||||
|
} else if (!isNaN(Number(value))) {
|
||||||
|
return Number(value);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
export type AllAppConfig = {
|
export type AppConfig = {
|
||||||
appName: string;
|
appName: string;
|
||||||
sessionDuration: string;
|
};
|
||||||
emailEnabled: string;
|
|
||||||
|
export type AllAppConfig = AppConfig & {
|
||||||
|
sessionDuration: number;
|
||||||
|
emailsVerified: boolean;
|
||||||
|
emailEnabled: boolean;
|
||||||
smtpHost: string;
|
smtpHost: string;
|
||||||
smtpPort: string;
|
smtpPort: number;
|
||||||
smtpFrom: string;
|
smtpFrom: string;
|
||||||
smtpUser: string;
|
smtpUser: string;
|
||||||
smtpPassword: string;
|
smtpPassword: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppConfig = AllAppConfig;
|
|
||||||
|
|
||||||
export type AppConfigRawResponse = {
|
export type AppConfigRawResponse = {
|
||||||
key: string;
|
key: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export function debounced<T extends (...args: any[]) => void>(func: T, delay: number) {
|
export function debounced<T extends (...args: any[]) => void>(func: T, delay: number) {
|
||||||
let debounceTimeout: number | undefined;
|
let debounceTimeout: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
return (...args: Parameters<T>) => {
|
return (...args: Parameters<T>) => {
|
||||||
if (debounceTimeout !== undefined) {
|
if (debounceTimeout !== undefined) {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let emailEnabled = $state(appConfig.emailEnabled == 'true');
|
let emailEnabled = $state(appConfig.emailEnabled);
|
||||||
|
|
||||||
const updatedAppConfig = {
|
const updatedAppConfig = {
|
||||||
emailEnabled: emailEnabled.toString(),
|
emailEnabled: appConfig.emailEnabled,
|
||||||
smtpHost: appConfig.smtpHost,
|
smtpHost: appConfig.smtpHost,
|
||||||
smtpPort: appConfig.smtpPort,
|
smtpPort: appConfig.smtpPort,
|
||||||
smtpUser: appConfig.smtpUser,
|
smtpUser: appConfig.smtpUser,
|
||||||
@@ -28,13 +28,13 @@
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
smtpHost: z.string().min(1),
|
smtpHost: z.string().min(1),
|
||||||
smtpPort: z.string().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()
|
||||||
});
|
});
|
||||||
|
|
||||||
const { inputs, ...form } = createForm< typeof formSchema>(formSchema, updatedAppConfig);
|
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
const data = form.validate();
|
const data = form.validate();
|
||||||
@@ -42,15 +42,15 @@
|
|||||||
isLoading = true;
|
isLoading = true;
|
||||||
await callback({
|
await callback({
|
||||||
...data,
|
...data,
|
||||||
emailEnabled: 'true'
|
emailEnabled: true
|
||||||
}).finally(() => (isLoading = false));
|
}).finally(() => (isLoading = false));
|
||||||
toast.success('Email configuration updated successfully');
|
toast.success('Email configuration updated successfully');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDisable() {
|
async function onDisable() {
|
||||||
await callback({ emailEnabled: 'false' });
|
|
||||||
emailEnabled = false;
|
emailEnabled = false;
|
||||||
|
await callback({ emailEnabled });
|
||||||
toast.success('Email disabled successfully');
|
toast.success('Email disabled successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<form onsubmit={onSubmit}>
|
<form onsubmit={onSubmit}>
|
||||||
<div class="mt-5 grid grid-cols-2 gap-5">
|
<div class="mt-5 grid grid-cols-2 gap-5">
|
||||||
<FormInput label="SMTP Host" bind:input={$inputs.smtpHost} />
|
<FormInput label="SMTP Host" bind:input={$inputs.smtpHost} />
|
||||||
<FormInput label="SMTP Port" bind:input={$inputs.smtpPort} />
|
<FormInput label="SMTP Port" type="number" bind:input={$inputs.smtpPort} />
|
||||||
<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} />
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
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 { Checkbox } from '$lib/components/ui/checkbox';
|
||||||
|
import { Label } from '$lib/components/ui/label';
|
||||||
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';
|
||||||
@@ -18,20 +20,14 @@
|
|||||||
|
|
||||||
const updatedAppConfig = {
|
const updatedAppConfig = {
|
||||||
appName: appConfig.appName,
|
appName: appConfig.appName,
|
||||||
sessionDuration: appConfig.sessionDuration
|
sessionDuration: appConfig.sessionDuration,
|
||||||
|
emailsVerified: appConfig.emailsVerified
|
||||||
};
|
};
|
||||||
|
|
||||||
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(
|
sessionDuration: z.number().min(1).max(43200),
|
||||||
(val) => {
|
emailsVerified: z.boolean()
|
||||||
const num = Number(val);
|
|
||||||
return Number.isInteger(num) && num >= 1 && num <= 43200;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
message: 'Session duration must be between 1 and 43200 minutes'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
||||||
@@ -49,9 +45,21 @@
|
|||||||
<FormInput label="Application Name" bind:input={$inputs.appName} />
|
<FormInput label="Application Name" bind:input={$inputs.appName} />
|
||||||
<FormInput
|
<FormInput
|
||||||
label="Session Duration"
|
label="Session Duration"
|
||||||
|
type="number"
|
||||||
description="The duration of a session in minutes before the user has to sign in again."
|
description="The duration of a session in minutes before the user has to sign in again."
|
||||||
bind:input={$inputs.sessionDuration}
|
bind:input={$inputs.sessionDuration}
|
||||||
/>
|
/>
|
||||||
|
<div class="items-top mt-5 flex space-x-2">
|
||||||
|
<Checkbox id="admin-privileges" bind:checked={$inputs.emailsVerified.value} />
|
||||||
|
<div class="grid gap-1.5 leading-none">
|
||||||
|
<Label for="admin-privileges" class="mb-0 text-sm font-medium leading-none">
|
||||||
|
Emails Verified
|
||||||
|
</Label>
|
||||||
|
<p class="text-muted-foreground text-[0.8rem]">
|
||||||
|
Whether the user's email should be marked as verified for the OIDC clients.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user