Actually mostly-compliant introspection endpoint, csrf and documentation
This commit is contained in:
parent
874b4804b9
commit
17821bfda3
19
.env.example
19
.env.example
@ -1,14 +1,27 @@
|
|||||||
|
# Front-end public URL, without leading slash
|
||||||
PUBLIC_URL=http://localhost:5173
|
PUBLIC_URL=http://localhost:5173
|
||||||
|
|
||||||
|
# Site name, displayed on the UI and in emails
|
||||||
PUBLIC_SITE_NAME=Amanita SSO
|
PUBLIC_SITE_NAME=Amanita SSO
|
||||||
|
|
||||||
|
# Database connection (mysql)
|
||||||
DATABASE_HOST=localhost
|
DATABASE_HOST=localhost
|
||||||
DATABASE_DB=icyauth
|
DATABASE_DB=icyauth
|
||||||
DATABASE_USER=icyauth
|
DATABASE_USER=icyauth
|
||||||
DATABASE_PASS=icyauth
|
DATABASE_PASS=icyauth
|
||||||
|
|
||||||
|
# Secret keys for sessions and challenges
|
||||||
|
# These keys should be rotated as part of regular maintenance
|
||||||
SESSION_SECRET=32 char key
|
SESSION_SECRET=32 char key
|
||||||
CHALLENGE_SECRET=64 char key
|
CHALLENGE_SECRET=64 char key
|
||||||
|
|
||||||
|
# OpenID Connect JWT (ID token) settings
|
||||||
|
# Private keys for JWTs are stored as files in the private directory
|
||||||
JWT_ALGORITHM=RS256
|
JWT_ALGORITHM=RS256
|
||||||
JWT_EXPIRATION=7d
|
JWT_EXPIRATION=7d
|
||||||
JWT_ISSUER=http://localhost:5173
|
JWT_ISSUER=http://localhost:5173
|
||||||
|
|
||||||
|
# SMTP settings
|
||||||
EMAIL_ENABLED=true
|
EMAIL_ENABLED=true
|
||||||
EMAIL_FROM=no-reply@icynet.eu
|
EMAIL_FROM=no-reply@icynet.eu
|
||||||
EMAIL_SMTP_HOST=mail.icynet.eu
|
EMAIL_SMTP_HOST=mail.icynet.eu
|
||||||
@ -16,7 +29,13 @@ EMAIL_SMTP_PORT=587
|
|||||||
EMAIL_SMTP_SECURE=false
|
EMAIL_SMTP_SECURE=false
|
||||||
EMAIL_SMTP_USER=
|
EMAIL_SMTP_USER=
|
||||||
EMAIL_SMTP_PASS=
|
EMAIL_SMTP_PASS=
|
||||||
|
|
||||||
|
# Enable new account registrations
|
||||||
REGISTRATIONS=true
|
REGISTRATIONS=true
|
||||||
|
|
||||||
|
# Trust the first proxy to give us the user's real IP
|
||||||
ADDRESS_HEADER=X-Forwarded-For
|
ADDRESS_HEADER=X-Forwarded-For
|
||||||
XFF_DEPTH=1
|
XFF_DEPTH=1
|
||||||
|
|
||||||
|
# Run database migrations automatically on startup
|
||||||
AUTO_MIGRATE=true
|
AUTO_MIGRATE=true
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { AUTO_MIGRATE, SESSION_SECRET } from '$env/static/private';
|
import { AUTO_MIGRATE, SESSION_SECRET } from '$env/static/private';
|
||||||
|
import { csrf } from '$lib/server/csrf';
|
||||||
import { DB } from '$lib/server/drizzle';
|
import { DB } from '$lib/server/drizzle';
|
||||||
import { runSeeds } from '$lib/server/drizzle/seeds';
|
import { runSeeds } from '$lib/server/drizzle/seeds';
|
||||||
import { JWT } from '$lib/server/jwt';
|
import { JWT } from '$lib/server/jwt';
|
||||||
|
import { sequence } from '@sveltejs/kit/hooks';
|
||||||
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
||||||
import { handleSession } from 'svelte-kit-cookie-session';
|
import { handleSession } from 'svelte-kit-cookie-session';
|
||||||
|
|
||||||
@ -14,6 +16,9 @@ if (AUTO_MIGRATE === 'true') {
|
|||||||
|
|
||||||
await runSeeds();
|
await runSeeds();
|
||||||
|
|
||||||
export const handle = handleSession({
|
export const handle = sequence(
|
||||||
|
csrf(['/oauth2/token', '/oauth2/introspect']),
|
||||||
|
handleSession({
|
||||||
secret: SESSION_SECRET
|
secret: SESSION_SECRET
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
@ -13,6 +13,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form {action} method="POST" use:enhancer={enhanceFn}>
|
<form {action} method="POST" use:enhancer={enhanceFn}>
|
||||||
<slot name="form" />
|
|
||||||
<Button {variant} type="submit"><slot /></Button>
|
<Button {variant} type="submit"><slot /></Button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
export class ApiUtils {
|
export class ApiUtils {
|
||||||
static json(data: unknown, status = 200): Response {
|
|
||||||
return new Response(JSON.stringify(data), {
|
|
||||||
status,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static redirect(url: string, status = 302): Response {
|
static redirect(url: string, status = 302): Response {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status,
|
status,
|
||||||
@ -14,4 +7,24 @@ export class ApiUtils {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getJsonOrFormBody(request: Request) {
|
||||||
|
if (request.headers.get('content-type')?.startsWith('application/json')) {
|
||||||
|
try {
|
||||||
|
const jsonBody = await request.json();
|
||||||
|
return jsonBody;
|
||||||
|
} catch {
|
||||||
|
// Try next...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formBody = await request.formData();
|
||||||
|
return Object.fromEntries(formBody);
|
||||||
|
} catch (err) {
|
||||||
|
// Skip...
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
33
src/lib/server/csrf.ts
Normal file
33
src/lib/server/csrf.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { json, text, type Handle } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
const isContentType = (request: Request, ...types: string[]) => {
|
||||||
|
const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? '';
|
||||||
|
return types.includes(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormContentType = (request: Request) =>
|
||||||
|
isContentType(request, 'application/x-www-form-urlencoded', 'multipart/form-data');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSRF protection copied from sveltekit but with the ability to turn it off for specific routes.
|
||||||
|
*/
|
||||||
|
export const csrf =
|
||||||
|
(allowedPaths: string[]): Handle =>
|
||||||
|
async ({ event, resolve }) => {
|
||||||
|
const forbidden =
|
||||||
|
event.request.method === 'POST' &&
|
||||||
|
event.request.headers.get('origin') !== event.url.origin &&
|
||||||
|
isFormContentType(event.request) &&
|
||||||
|
!allowedPaths.includes(event.url.pathname);
|
||||||
|
|
||||||
|
if (forbidden) {
|
||||||
|
const csrfError = `Cross-site ${event.request.method} form submissions are forbidden`;
|
||||||
|
if (event.request.headers.get('accept') === 'application/json') {
|
||||||
|
return json({ error: 'forbidden', error_description: csrfError }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return text(csrfError, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(event);
|
||||||
|
};
|
@ -9,10 +9,11 @@ import {
|
|||||||
type JWK,
|
type JWK,
|
||||||
type KeyLike
|
type KeyLike
|
||||||
} from 'jose';
|
} from 'jose';
|
||||||
|
import { join } from 'path';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate JWTs using the following commands:
|
* Generate JWT keys using the following commands:
|
||||||
* Private: openssl genpkey -out jwt.private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
|
* Private: openssl genpkey -out jwt.private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
|
||||||
* Public: openssl rsa -in jwt.private.pem -pubout -outform PEM -out jwt.public.pem
|
* Public: openssl rsa -in jwt.private.pem -pubout -outform PEM -out jwt.public.pem
|
||||||
*/
|
*/
|
||||||
@ -23,8 +24,10 @@ export class JWT {
|
|||||||
static jwksKid: string;
|
static jwksKid: string;
|
||||||
|
|
||||||
static async init() {
|
static async init() {
|
||||||
const privateKeyFile = await readFile('private/jwt.private.pem', { encoding: 'utf-8' });
|
const privateKeyFile = await readFile(join('private', 'jwt.private.pem'), {
|
||||||
const publicKeyFile = await readFile('private/jwt.public.pem', { encoding: 'utf-8' });
|
encoding: 'utf-8'
|
||||||
|
});
|
||||||
|
const publicKeyFile = await readFile(join('private', 'jwt.public.pem'), { encoding: 'utf-8' });
|
||||||
JWT.privateKey = await importPKCS8(privateKeyFile, JWT_ALGORITHM);
|
JWT.privateKey = await importPKCS8(privateKeyFile, JWT_ALGORITHM);
|
||||||
JWT.publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM);
|
JWT.publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM);
|
||||||
JWT.jwks = await exportJWK(JWT.publicKey);
|
JWT.jwks = await exportJWK(JWT.publicKey);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { InvalidRequest } from '../error';
|
import { ApiUtils } from '$lib/server/api-utils';
|
||||||
import { OAuth2AccessTokens } from '../model';
|
import { InvalidClient, InvalidRequest, UnauthorizedClient } from '../error';
|
||||||
|
import { OAuth2AccessTokens, OAuth2Clients } from '../model';
|
||||||
import { OAuth2Response } from '../response';
|
import { OAuth2Response } from '../response';
|
||||||
|
|
||||||
export class OAuth2IntrospectionController {
|
export class OAuth2IntrospectionController {
|
||||||
static postHandler = async ({ request, url }: { request: Request; url: URL }) => {
|
static postHandler = async ({ request, url }: { request: Request; url: URL }) => {
|
||||||
const body = await request.json().catch(() => ({}));
|
const body = await ApiUtils.getJsonOrFormBody(request);
|
||||||
|
|
||||||
let clientId: string | null = null;
|
let clientId: string | null = null;
|
||||||
let clientSecret: string | null = null;
|
let clientSecret: string | null = null;
|
||||||
@ -12,7 +13,7 @@ export class OAuth2IntrospectionController {
|
|||||||
if (body.client_id && body.client_secret) {
|
if (body.client_id && body.client_secret) {
|
||||||
clientId = body.client_id as string;
|
clientId = body.client_id as string;
|
||||||
clientSecret = body.client_secret as string;
|
clientSecret = body.client_secret as string;
|
||||||
console.debug('Client credentials parsed from body parameters ', clientId, clientSecret);
|
// console.debug('Client credentials parsed from body parameters ', clientId, clientSecret);
|
||||||
} else {
|
} else {
|
||||||
if (!request.headers?.has('authorization')) {
|
if (!request.headers?.has('authorization')) {
|
||||||
throw new InvalidRequest('No authorization header passed');
|
throw new InvalidRequest('No authorization header passed');
|
||||||
@ -34,23 +35,34 @@ export class OAuth2IntrospectionController {
|
|||||||
|
|
||||||
clientId = pieces[0];
|
clientId = pieces[0];
|
||||||
clientSecret = pieces[1];
|
clientSecret = pieces[1];
|
||||||
console.debug('Client credentials parsed from basic auth header: ', clientId, clientSecret);
|
// console.debug('Client credentials parsed from basic auth header: ', clientId, clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!body.token) {
|
if (!body.token) {
|
||||||
throw new InvalidRequest('Token not provided in request body');
|
throw new InvalidRequest('Token not provided in request body');
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await OAuth2AccessTokens.fetchByToken(body.token);
|
const client = await OAuth2Clients.fetchById(clientId);
|
||||||
if (!token) {
|
|
||||||
throw new InvalidRequest('Token does not exist');
|
if (!client) {
|
||||||
|
throw new InvalidClient('Client not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = OAuth2Clients.checkSecret(client, clientSecret);
|
||||||
|
if (!valid) {
|
||||||
|
throw new UnauthorizedClient('The client authentication was invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await OAuth2AccessTokens.fetchByToken(body.token);
|
||||||
|
if (!token || token.clientIdPub !== clientId || token.expires_at.getTime() < Date.now()) {
|
||||||
|
return OAuth2Response.response(url, { active: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ttl = OAuth2AccessTokens.getTTL(token);
|
|
||||||
const resObj = {
|
const resObj = {
|
||||||
token_type: 'bearer',
|
active: true,
|
||||||
token: token.token,
|
scope: token.scope || '',
|
||||||
expires_in: Math.floor(ttl / 1000)
|
client_id: clientId,
|
||||||
|
exp: Math.floor(token.expires_at.getTime() / 1000)
|
||||||
};
|
};
|
||||||
|
|
||||||
return OAuth2Response.response(url, resObj);
|
return OAuth2Response.response(url, resObj);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ApiUtils } from '$lib/server/api-utils';
|
||||||
import {
|
import {
|
||||||
InvalidRequest,
|
InvalidRequest,
|
||||||
InvalidClient,
|
InvalidClient,
|
||||||
@ -16,12 +17,12 @@ export class OAuth2TokenController {
|
|||||||
let clientSecret: string | null = null;
|
let clientSecret: string | null = null;
|
||||||
let grantType: string | null = null;
|
let grantType: string | null = null;
|
||||||
|
|
||||||
const body = await request.json().catch(() => ({}));
|
const body = await ApiUtils.getJsonOrFormBody(request);
|
||||||
|
|
||||||
if (body.client_id && body.client_secret) {
|
if (body.client_id && body.client_secret) {
|
||||||
clientId = body.client_id as string;
|
clientId = body.client_id as string;
|
||||||
clientSecret = body.client_secret as string;
|
clientSecret = body.client_secret as string;
|
||||||
console.debug('Client credentials parsed from body parameters', clientId, clientSecret);
|
// console.debug('Client credentials parsed from body parameters', clientId, clientSecret);
|
||||||
} else {
|
} else {
|
||||||
if (!request.headers?.has('authorization')) {
|
if (!request.headers?.has('authorization')) {
|
||||||
throw new InvalidRequest('No authorization header passed');
|
throw new InvalidRequest('No authorization header passed');
|
||||||
@ -43,7 +44,7 @@ export class OAuth2TokenController {
|
|||||||
|
|
||||||
clientId = pieces[0];
|
clientId = pieces[0];
|
||||||
clientSecret = pieces[1];
|
clientSecret = pieces[1];
|
||||||
console.debug('Client credentials parsed from basic auth header:', clientId, clientSecret);
|
// console.debug('Client credentials parsed from basic auth header:', clientId, clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!body.grant_type) {
|
if (!body.grant_type) {
|
||||||
@ -51,7 +52,7 @@ export class OAuth2TokenController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
grantType = body.grant_type as string;
|
grantType = body.grant_type as string;
|
||||||
console.debug('Parameter grant_type is', grantType);
|
// console.debug('Parameter grant_type is', grantType);
|
||||||
|
|
||||||
const client = await OAuth2Clients.fetchById(clientId);
|
const client = await OAuth2Clients.fetchById(clientId);
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ export class OAuth2TokenController {
|
|||||||
if (!OAuth2Clients.checkGrantType(client, grantType) && grantType !== 'refresh_token') {
|
if (!OAuth2Clients.checkGrantType(client, grantType) && grantType !== 'refresh_token') {
|
||||||
throw new UnauthorizedClient('Invalid grant type for the client');
|
throw new UnauthorizedClient('Invalid grant type for the client');
|
||||||
} else {
|
} else {
|
||||||
console.debug('Grant type check passed');
|
// console.debug('Grant type check passed');
|
||||||
}
|
}
|
||||||
|
|
||||||
let tokenResponse: OAuth2TokenResponse = {};
|
let tokenResponse: OAuth2TokenResponse = {};
|
||||||
|
@ -54,7 +54,7 @@ export async function authorizationCode(
|
|||||||
throw new InvalidGrant('Code not found');
|
throw new InvalidGrant('Code not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Code fetched', code);
|
// console.debug('Code fetched', code);
|
||||||
|
|
||||||
const scope = code.scope || '';
|
const scope = code.scope || '';
|
||||||
const cleanScope = OAuth2Clients.transformScope(scope);
|
const cleanScope = OAuth2Clients.transformScope(scope);
|
||||||
@ -84,11 +84,11 @@ export async function authorizationCode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Code passed PCKE check');
|
// console.debug('Code passed PCKE check');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!OAuth2Clients.checkGrantType(client, 'refresh_token')) {
|
if (!OAuth2Clients.checkGrantType(client, 'refresh_token')) {
|
||||||
console.debug('Client does not allow grant type refresh_token, skip creation');
|
// console.debug('Client does not allow grant type refresh_token, skip creation');
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
respObj.refresh_token = await OAuth2RefreshTokens.create(userId, clientId, scope);
|
respObj.refresh_token = await OAuth2RefreshTokens.create(userId, clientId, scope);
|
||||||
@ -111,7 +111,7 @@ export async function authorizationCode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
respObj.expires_in = OAuth2Tokens.tokenTtl;
|
respObj.expires_in = OAuth2Tokens.tokenTtl;
|
||||||
console.debug('Access token saved:', respObj.access_token);
|
// console.debug('Access token saved:', respObj.access_token);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await OAuth2Codes.removeByCode(providedCode);
|
await OAuth2Codes.removeByCode(providedCode);
|
||||||
|
@ -15,6 +15,10 @@ export interface OAuth2TokenResponse {
|
|||||||
expires_in?: number;
|
expires_in?: number;
|
||||||
token_type?: string;
|
token_type?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
|
active?: boolean;
|
||||||
|
scope?: string;
|
||||||
|
client_id?: string;
|
||||||
|
exp?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OAuth2Response {
|
export class OAuth2Response {
|
||||||
@ -116,7 +120,8 @@ export class OAuth2Response {
|
|||||||
fragment: boolean = false
|
fragment: boolean = false
|
||||||
) {
|
) {
|
||||||
if (redirectUri) {
|
if (redirectUri) {
|
||||||
redirectUri += fragment ? '#' : redirectUri.indexOf('?') === -1 ? '?' : '&';
|
const searchJoinChar = redirectUri.includes('?') ? '&' : '?';
|
||||||
|
redirectUri += fragment ? '#' : searchJoinChar;
|
||||||
|
|
||||||
if (url.searchParams.has('state')) {
|
if (url.searchParams.has('state')) {
|
||||||
obj.state = url.searchParams.get('state') as string;
|
obj.state = url.searchParams.get('state') as string;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { JWT_ALGORITHM } from '$env/static/private';
|
import { JWT_ALGORITHM } from '$env/static/private';
|
||||||
import { ApiUtils } from '$lib/server/api-utils';
|
|
||||||
import { JWT } from '$lib/server/jwt';
|
import { JWT } from '$lib/server/jwt';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const GET = async () =>
|
export const GET = async () =>
|
||||||
ApiUtils.json({
|
json({
|
||||||
keys: [{ alg: JWT_ALGORITHM, kid: JWT.jwksKid, ...JWT.jwks, use: 'sig' }]
|
keys: [{ alg: JWT_ALGORITHM, kid: JWT.jwksKid, ...JWT.jwks, use: 'sig' }]
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { JWT_ALGORITHM, JWT_ISSUER } from '$env/static/private';
|
import { JWT_ALGORITHM, JWT_ISSUER } from '$env/static/private';
|
||||||
import { PUBLIC_URL } from '$env/static/public';
|
import { PUBLIC_URL } from '$env/static/public';
|
||||||
import { ApiUtils } from '$lib/server/api-utils';
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const GET = async () =>
|
export const GET = async () =>
|
||||||
ApiUtils.json({
|
json({
|
||||||
issuer: JWT_ISSUER,
|
issuer: JWT_ISSUER,
|
||||||
authorization_endpoint: `${PUBLIC_URL}/oauth2/authorize`,
|
authorization_endpoint: `${PUBLIC_URL}/oauth2/authorize`,
|
||||||
token_endpoint: `${PUBLIC_URL}/oauth2/token`,
|
token_endpoint: `${PUBLIC_URL}/oauth2/token`,
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import { PUBLIC_SITE_NAME } from '$env/static/public';
|
import { PUBLIC_SITE_NAME } from '$env/static/public';
|
||||||
import FormErrors from '$lib/components/form/FormErrors.svelte';
|
import FormErrors from '$lib/components/form/FormErrors.svelte';
|
||||||
import TitleRow from '$lib/components/container/TitleRow.svelte';
|
import TitleRow from '$lib/components/container/TitleRow.svelte';
|
||||||
|
import ActionButton from '$lib/components/ActionButton.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
export let form: ActionData;
|
export let form: ActionData;
|
||||||
@ -180,9 +181,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{#if data.hasAvatar}
|
{#if data.hasAvatar}
|
||||||
<form action="?/removeAvatar" method="POST" use:enhance>
|
<ActionButton action="?/removeAvatar">{$t('account.avatar.remove')}</ActionButton>
|
||||||
<Button variant="link" type="submit">{$t('account.avatar.remove')}</Button>
|
|
||||||
</form>
|
|
||||||
{/if}
|
{/if}
|
||||||
</ViewColumn>
|
</ViewColumn>
|
||||||
</AvatarCard>
|
</AvatarCard>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { PUBLIC_URL } from '$env/static/public';
|
import { PUBLIC_URL } from '$env/static/public';
|
||||||
import { ApiUtils } from '$lib/server/api-utils.js';
|
|
||||||
import type { User } from '$lib/server/drizzle/schema.js';
|
import type { User } from '$lib/server/drizzle/schema.js';
|
||||||
import { OAuth2BearerController } from '$lib/server/oauth2/controller/bearer.js';
|
import { OAuth2BearerController } from '$lib/server/oauth2/controller/bearer.js';
|
||||||
import { AccessDenied, OAuth2Error } from '$lib/server/oauth2/error.js';
|
import { AccessDenied, OAuth2Error } from '$lib/server/oauth2/error.js';
|
||||||
import { OAuth2Clients } from '$lib/server/oauth2/model/client.js';
|
import { OAuth2Clients } from '$lib/server/oauth2/model/client.js';
|
||||||
import { OAuth2Response } from '$lib/server/oauth2/response.js';
|
import { OAuth2Response } from '$lib/server/oauth2/response.js';
|
||||||
import { Users } from '$lib/server/users/index.js';
|
import { Users } from '$lib/server/users/index.js';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const GET = async ({ request, url, locals }) => {
|
export const GET = async ({ request, url, locals }) => {
|
||||||
let user: User | undefined = undefined;
|
let user: User | undefined = undefined;
|
||||||
@ -64,5 +64,5 @@ export const GET = async ({ request, url, locals }) => {
|
|||||||
userData.privileges = await Users.getUserPrivileges(user, clientId);
|
userData.privileges = await Users.getUserPrivileges(user, clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiUtils.json(userData);
|
return json(userData);
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,12 @@ const config = {
|
|||||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
adapter: adapter()
|
adapter: adapter(),
|
||||||
|
|
||||||
|
// This is reimplemented in hooks.server.ts to allow certain endpoints
|
||||||
|
csrf: {
|
||||||
|
checkOrigin: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user