From 243fe982b1120eb66c77385232bde50f63932f51 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sun, 2 Jun 2024 14:53:31 +0300 Subject: [PATCH] filters, page titles --- .../admin/AdminPrivilegesSelect.svelte | 2 +- src/lib/components/container/TitleRow.svelte | 2 +- src/lib/i18n/en/admin.json | 12 ++- src/lib/i18n/en/common.json | 3 +- src/lib/server/oauth2/model/client.ts | 95 ++++++++++++++++++- src/lib/server/users/admin.ts | 8 +- src/lib/server/users/index.ts | 10 +- src/routes/ssoadmin/oauth2/+page.svelte | 41 ++++++-- .../ssoadmin/oauth2/[uuid]/+page.server.ts | 66 ++++++++----- .../ssoadmin/oauth2/[uuid]/+page.svelte | 68 ++++++++++++- .../oauth2/[uuid]/user/[user]/+page.server.ts | 94 ++++++++++++++++++ .../oauth2/[uuid]/user/[user]/+page.svelte | 38 ++++++++ .../ssoadmin/oauth2/new/+page.server.ts | 48 ++++++++++ src/routes/ssoadmin/oauth2/new/+page.svelte | 43 +++++++++ src/routes/ssoadmin/users/+page.svelte | 35 +++++-- src/routes/ssoadmin/users/[uuid]/+page.svelte | 8 ++ 16 files changed, 516 insertions(+), 57 deletions(-) create mode 100644 src/routes/ssoadmin/oauth2/[uuid]/user/[user]/+page.server.ts create mode 100644 src/routes/ssoadmin/oauth2/[uuid]/user/[user]/+page.svelte create mode 100644 src/routes/ssoadmin/oauth2/new/+page.server.ts create mode 100644 src/routes/ssoadmin/oauth2/new/+page.svelte diff --git a/src/lib/components/admin/AdminPrivilegesSelect.svelte b/src/lib/components/admin/AdminPrivilegesSelect.svelte index c6d40ea..84d42b6 100644 --- a/src/lib/components/admin/AdminPrivilegesSelect.svelte +++ b/src/lib/components/admin/AdminPrivilegesSelect.svelte @@ -86,7 +86,7 @@ } .transfer-box > :global(.column):nth-child(2) { - margin-top: 1.35rem; + margin-top: 1.8rem; } .transfer-box :global(.form-control) { diff --git a/src/lib/components/container/TitleRow.svelte b/src/lib/components/container/TitleRow.svelte index 360c74d..baa191f 100644 --- a/src/lib/components/container/TitleRow.svelte +++ b/src/lib/components/container/TitleRow.svelte @@ -8,7 +8,7 @@ display: flex; align-items: center; justify-content: space-between; - margin-bottom: 1.25rem; + margin-bottom: 1.3rem; } .title-row > :global(*):first-child { margin: 0; diff --git a/src/lib/i18n/en/admin.json b/src/lib/i18n/en/admin.json index 987ccac..b918084 100644 --- a/src/lib/i18n/en/admin.json +++ b/src/lib/i18n/en/admin.json @@ -30,6 +30,7 @@ }, "oauth2": { "title": "OAuth2 applications", + "new": "Create a new application", "clientTitle": "Application name", "clientId": "Client ID", "clientSecret": "Client secret", @@ -64,7 +65,8 @@ "addHint": "You may assign application-specific privileges to your authorized users. You may use this system for permissions or for tagging your users, all up to you!", "remove": "Remove", "manage": "Manage user privileges", - "new": "Add a new privilege" + "new": "Add a new privilege", + "edit": "Edit privileges" }, "urls": { "title": "Application URLs", @@ -79,6 +81,14 @@ }, "add": "Add URL" }, + "apis": { + "title": "OAuth2 APIs", + "authorize": "OAuth2 Authorization endpoint", + "token": "OAuth2 Token endpoint", + "introspect": "OAuth2 Introspection endpoint", + "userinfo": "User information endpoint (Bearer)", + "openid": "OpenID Connect configuration" + }, "grantTexts": { "authorization_code": "Authorization code", "client_credentials": "Client credentials", diff --git a/src/lib/i18n/en/common.json b/src/lib/i18n/en/common.json index ca24a9f..b7d2eed 100644 --- a/src/lib/i18n/en/common.json +++ b/src/lib/i18n/en/common.json @@ -16,5 +16,6 @@ }, "available": "Available", "current": "Current", - "remove": "Remove" + "remove": "Remove", + "filter": "Filter" } diff --git a/src/lib/server/oauth2/model/client.ts b/src/lib/server/oauth2/model/client.ts index 752fe4b..2683526 100644 --- a/src/lib/server/oauth2/model/client.ts +++ b/src/lib/server/oauth2/model/client.ts @@ -1,17 +1,20 @@ +import { CryptoUtils } from '$lib/server/crypto-utils'; import { db, oauth2Client, + oauth2ClientAuthorization, oauth2ClientManager, oauth2ClientUrl, privilege, user, + userPrivilegesPrivilege, type OAuth2Client, type OAuth2ClientUrl, type User } from '$lib/server/drizzle'; import { Uploads } from '$lib/server/upload'; import type { PaginationMeta } from '$lib/types'; -import { and, count, eq, ilike, like, or } from 'drizzle-orm'; +import { and, count, eq, like, or, sql } from 'drizzle-orm'; export enum OAuth2ClientURLType { REDIRECT_URI = 'redirect_uri', @@ -27,6 +30,17 @@ export interface OAuth2ClientAdminListItem urls: Omit[]; } +export interface OAuth2AuthorizedUser { + uuid: string; + name: string; + current: boolean; + privileges: { id: number; name: string }[]; +} + +export interface OAuth2ManagerUser { + email: string; +} + export class OAuth2Clients { public static availableGrantTypes = [ 'authorization_code', @@ -109,6 +123,52 @@ export class OAuth2Clients { )?.length; } + static async getAuthorizedUsers(client: OAuth2Client, userUuid?: string) { + const junkList = await db + .select() + .from(oauth2ClientAuthorization) + .innerJoin(user, eq(user.id, oauth2ClientAuthorization.userId)) + .leftJoin(userPrivilegesPrivilege, eq(userPrivilegesPrivilege.userId, user.id)) + .leftJoin( + privilege, + and( + eq(userPrivilegesPrivilege.privilegeId, privilege.id), + eq(privilege.clientId, client.id) + ) + ) + .where( + and( + eq(oauth2ClientAuthorization.clientId, client.id), + userUuid ? eq(user.uuid, userUuid) : undefined + ) + ); + + const list = junkList.reduce((accum, dbo) => { + let user = accum.find((entry) => entry.uuid === dbo.user.uuid); + if (!user) { + user = { + uuid: dbo.user.uuid, + name: dbo.user.display_name, + current: false, + privileges: [] + }; + accum.push(user); + } + + if (dbo.privilege && !user.privileges.some(({ id }) => id === dbo.privilege?.id)) { + user.privileges.push({ id: dbo.privilege.id, name: dbo.privilege.name }); + } + + if (dbo.o_auth2_client_authorization?.current === 1) { + user.current = true; + } + + return accum; + }, []); + + return list; + } + static checkSecret(client: OAuth2Client, secret: string) { return client.client_secret === secret; } @@ -152,7 +212,7 @@ export class OAuth2Clients { listAll?: boolean; } ) { - const filterText = `%${filters?.filter}%`; + const filterText = `%${filters?.filter?.toLowerCase()}%`; const limit = filters?.limit || 20; const allowedClients = db .select({ id: oauth2Client.id }) @@ -165,7 +225,10 @@ export class OAuth2Clients { : undefined, filters?.clientId ? eq(oauth2Client.client_id, filters.clientId) : undefined, filters?.filter - ? or(ilike(oauth2Client.title, filterText), like(oauth2Client.client_id, filterText)) + ? or( + sql`lower(${oauth2Client.title}) like ${filterText}`, + like(oauth2Client.client_id, filterText) + ) : undefined ) ) @@ -240,6 +303,32 @@ export class OAuth2Clients { }; } + static async createClient(subject: User, title: string, redirect: string, description?: string) { + const uid = CryptoUtils.createUUID(); + const secret = CryptoUtils.generateSecret(); + + const [retval] = await db.insert(oauth2Client).values({ + title, + description, + client_id: uid, + client_secret: secret, + grants: 'authorization_code', + scope: 'profile', + ownerId: subject.id, + created_at: new Date(), + activated: 1, + verified: 0 + }); + + await db.insert(oauth2ClientUrl).values({ + type: 'redirect_uri', + url: redirect, + clientId: retval.insertId + }); + + return uid; + } + static async deleteClient(client: OAuth2Client) { if (client.pictureId) { await Uploads.removeClientAvatar(client); diff --git a/src/lib/server/users/admin.ts b/src/lib/server/users/admin.ts index 3acfcaf..c7f1297 100644 --- a/src/lib/server/users/admin.ts +++ b/src/lib/server/users/admin.ts @@ -1,4 +1,4 @@ -import { count, eq, ilike, like, or } from 'drizzle-orm'; +import { count, eq, like, or, sql } from 'drizzle-orm'; import { db, privilege, @@ -58,9 +58,9 @@ export class UsersAdmin { const searchExpression = filter ? or( like(user.uuid, subfilter), - ilike(user.username, subfilter), - ilike(user.display_name, subfilter), - ilike(user.email, subfilter) + sql`lower(${user.username}) like ${subfilter}`, + sql`lower(${user.display_name}) like ${subfilter}`, + sql`lower(${user.email}) like ${subfilter}` ) : undefined; diff --git a/src/lib/server/users/index.ts b/src/lib/server/users/index.ts index 3777a6a..fd03e7b 100644 --- a/src/lib/server/users/index.ts +++ b/src/lib/server/users/index.ts @@ -241,13 +241,19 @@ export class Users { ); } - static async setUserPrivileges(subject: User, privilegeIds: number[]) { + static async setUserPrivileges(subject: User, privilegeIds: number[], clientId?: number) { const current = await db .select({ privilegeId: userPrivilegesPrivilege.privilegeId }) .from(userPrivilegesPrivilege) - .where(eq(userPrivilegesPrivilege.userId, subject.id)); + .innerJoin(privilege, eq(privilege.id, userPrivilegesPrivilege.privilegeId)) + .where( + and( + eq(userPrivilegesPrivilege.userId, subject.id), + clientId ? eq(privilege.clientId, clientId) : undefined + ) + ); const toRemoveIds = current.reduce( (list, { privilegeId }) => diff --git a/src/routes/ssoadmin/oauth2/+page.svelte b/src/routes/ssoadmin/oauth2/+page.svelte index 4406cbd..ef5f50f 100644 --- a/src/routes/ssoadmin/oauth2/+page.svelte +++ b/src/routes/ssoadmin/oauth2/+page.svelte @@ -3,21 +3,42 @@ import { t } from '$lib/i18n'; import type { PageData } from './$types'; import ClientCard from '$lib/components/admin/AdminClientCard.svelte'; + import TitleRow from '$lib/components/container/TitleRow.svelte'; + import ColumnView from '$lib/components/container/ColumnView.svelte'; + import { PUBLIC_SITE_NAME } from '$env/static/public'; + import FormControl from '$lib/components/form/FormControl.svelte'; + import { page } from '$app/stores'; export let data: PageData; -

{$t('admin.oauth2.title')}

+ + {$t('admin.oauth2.title')} - {PUBLIC_SITE_NAME} {$t('admin.title')} + -
- - {#each data.list as client} - - - - {/each} - -
+ +

{$t('admin.oauth2.title')}

+ {$t('admin.oauth2.new')} +
+ + +
+ + + + +
+ +
+ + {#each data.list as client} + + + + {/each} + +
+