Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configuration through .env variables #102

Merged
merged 9 commits into from
Nov 11, 2024
30 changes: 17 additions & 13 deletions docs/docs/features/oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@ The specific steps to do this depend on the identity provider you are using, but

## Configuration

To configure OAuth authentication in AirTrail, go to the `Settings` page and click on the `OAuth` tab (you need to be an
admin to access this page).
Here you can enter the following settings:

| Setting | Default | Description |
|---------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Enabled | `false` | Whether to enable OAuth authentication. |
| Issuer URL | | The URL of the OIDC issuer (e.g. `https://accounts.google.com/.well-known/openid-configuration`). |
| Client ID | | The client ID of the OAuth client you created in your identity provider. |
| Client Secret | | The client secret of the OAuth client you created in your identity provider. |
| Scope | openid profile | The scopes to send with the request. |
| Auto Register | `true` | Whether to automatically register new users if no existing AirTrail user is found for the username. |
| Auto Login | `false` | Whether to automatically launch the OAuth login flow when a user visits the login page. To prevent redirection, add `?autoLogin=false` to the end of the url. |
To configure OAuth authentication in AirTrail, either go to the `Settings` page and click on the `OAuth` tab (you need
to be an
admin to access this page), or configure OAuth through the `.env` file.

The same settings are available in both the `.env` file and the settings page.
On startup, AirTrail will check the `.env` file for OAuth settings and use them if they are present.
Settings that are configured in the `.env` file will not be editable in the settings page.

| Setting | Env. Var. Name | Default | Description |
|---------------|-----------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Enabled | `OAUTH_ENABLED` | `false` | Whether to enable OAuth authentication. |
| Issuer URL | `OAUTH_ISSUER_URL` | | The URL of the OIDC issuer (e.g. `https://accounts.google.com/.well-known/openid-configuration`). |
| Client ID | `OAUTH_CLIENT_ID` | The client ID of the OAuth client you created in your identity provider. |
| Client Secret | `OAUTH_CLIENT_SECRET` | | The client secret of the OAuth client you created in your identity provider. |
| Scope | `OAUTH_SCOPE` | openid profile | The scopes to send with the request. |
| Auto Register | `OAUTH_AUTO_REGISTER` | `true` | Whether to automatically register new users if no existing AirTrail user is found for the username. |
| Auto Login | `OAUTH_AUTO_LOGIN` | `false` | Whether to automatically launch the OAuth login flow when a user visits the login page. To prevent redirection, add `?autoLogin=false` to the end of the url. |
29 changes: 29 additions & 0 deletions prisma/migrations/20241108100132_app_config/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
ALTER TABLE app_config
ADD COLUMN config JSONB NOT NULL DEFAULT '{}';

WITH first_row AS (SELECT id
FROM app_config
ORDER BY id ASC
LIMIT 1)
UPDATE app_config
SET config = JSONB_BUILD_OBJECT(
'oauth', JSONB_BUILD_OBJECT(
'enabled', enabled,
'issuerUrl', issuer_url,
'clientId', client_id,
'clientSecret', client_secret,
'scope', scope,
'autoRegister', auto_register,
'autoLogin', auto_login
)
)
WHERE id in (SELECT id FROM first_row);

ALTER TABLE app_config
DROP COLUMN enabled,
DROP COLUMN issuer_url,
DROP COLUMN client_id,
DROP COLUMN client_secret,
DROP COLUMN scope,
DROP COLUMN auto_register,
DROP COLUMN auto_login;
13 changes: 2 additions & 11 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,8 @@ generator kysely {
}

model app_config {
id Int @id @default(autoincrement())

/// OIDC

enabled Boolean @default(false)
issuer_url String?
client_id String?
client_secret String?
scope String @default("openid profile")
auto_register Boolean @default(true)
auto_login Boolean @default(false)
id Int @id @default(autoincrement())
config Json @default("{}")
}

model user {
Expand Down
3 changes: 0 additions & 3 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import type { AppConfig } from '$lib/db/types';

declare global {
namespace App {
interface Locals {
user: import('lucia').User | null;
session: import('lucia').Session | null;
appConfig: AppConfig | null;
}

interface PageData {
Expand Down
16 changes: 13 additions & 3 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { lucia } from '$lib/server/auth';
import type { Cookie } from 'lucia';
import { fetchAppConfig } from '$lib/server/utils/config';
import { appConfig } from '$lib/server/utils/config';

async function loadConfig() {
await appConfig.get();
await appConfig.loadFromEnv();
}

const setup = loadConfig().catch((err) => {
console.error('Error loading app config from .env:', err);
process.exit(-1);
});

export async function handle({ event, resolve }) {
await setup;

const sessionId = event.cookies.get(lucia.sessionCookieName);
if (!sessionId) {
event.locals.user = null;
event.locals.session = null;
event.locals.appConfig = await fetchAppConfig();
return resolve(event);
}

Expand All @@ -28,6 +39,5 @@ export async function handle({ event, resolve }) {

event.locals.user = user;
event.locals.session = session;
event.locals.appConfig = await fetchAppConfig();
return resolve(event);
}
43 changes: 43 additions & 0 deletions src/lib/components/helpers/Locked.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { cn } from '$lib/utils';
import * as Tooltip from '$lib/components/ui/tooltip';
import { Lock } from '@o7/icon/lucide';

let {
locked,
tooltip,
class: className,
children,
}: {
locked: boolean;
tooltip: Snippet;
class?: string;
children: Snippet;
} = $props();
</script>

<Tooltip.Root delayDuration={0} disabled={!locked}>
<Tooltip.Trigger>
{#snippet child({ props })}
<div class={cn('relative', { 'cursor-pointer': locked })} {...props}>
<div
class={cn(
{ 'opacity-80 blur-[1px] select-none pointer-events-none': locked },
className,
)}
>
{@render children()}
</div>
{#if locked}
<div class="absolute top-1 right-1">
<Lock />
</div>
{/if}
</div>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content sideOffset={20}>
{@render tooltip()}
</Tooltip.Content>
</Tooltip.Root>
1 change: 1 addition & 0 deletions src/lib/components/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as Redirect } from './Redirect.svelte';
export { default as ScreenSize } from './ScreenSize.svelte';
export { default as OnResizeEnd } from './OnResizeEnd.svelte';
export { default as Confirm } from './Confirm.svelte';
export { default as Locked } from './Locked.svelte';
Loading
Loading