feat(oidc): use generic implementation

This commit is contained in:
DrummyFloyd 2025-01-31 16:21:44 +01:00
parent f9aa278883
commit 17892e64ad
No known key found for this signature in database
GPG Key ID: 20AC86EE49130589
11 changed files with 82 additions and 48 deletions

View File

@ -1,6 +1,6 @@
# Configuration reference: http://docs.postiz.com/configuration/reference
# === Required Settings
# === Required Settings
DATABASE_URL="postgresql://postiz-user:postiz-password@localhost:5432/postiz-db-local"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="random string for your JWT secret, make it long"
@ -20,7 +20,6 @@ CLOUDFLARE_BUCKETNAME="your-bucket-name"
CLOUDFLARE_BUCKET_URL="https://your-bucket-url.r2.cloudflarestorage.com/"
CLOUDFLARE_REGION="auto"
# === Common optional Settings
## This is a dummy key, you must create your own from Resend.
@ -32,7 +31,7 @@ CLOUDFLARE_REGION="auto"
#DISABLE_REGISTRATION=false
# Where will social media icons be saved - local or cloudflare.
STORAGE_PROVIDER="local"
STORAGE_PROVIDER="local"
# Your upload directory path if you host your files locally, otherwise Cloudflare will be used.
#UPLOAD_DIRECTORY=""
@ -40,7 +39,6 @@ STORAGE_PROVIDER="local"
# Your upload directory path if you host your files locally, otherwise Cloudflare will be used.
#NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY=""
# Social Media API Settings
X_API_KEY=""
X_API_SECRET=""
@ -91,8 +89,13 @@ STRIPE_SIGNING_KEY_CONNECT=""
# Developer Settings
NX_ADD_PLUGINS=false
IS_GENERAL="true" # required for now
AUTHENTIK_OIDC="false"
AUTHENTIK_URL=""
AUTHENTIK_CLIENT_ID=""
AUTHENTIK_CLIENT_SECRET=""
AUTHENTIK_SCOPE=""
NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME="Authentik"
NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL="https://raw.githubusercontent.com/walkxcode/dashboard-icons/master/png/authentik.png"
POSTIZ_GENERIC_OAUTH="false"
POSTIZ_OAUTH_URL="https://auth.example.com"
POSTIZ_OAUTH_AUTH_URL="https://auth.example.com/application/o/authorize"
POSTIZ_OAUTH_TOKEN_URL="https://auth.example.com/application/o/token"
POSTIZ_OAUTH_USERINFO_URL="https://authentik.example.com/application/o/userinfo"
POSTIZ_OAUTH_CLIENT_ID=""
POSTIZ_OAUTH_CLIENT_SECRET=""
# POSTIZ_OAUTH_SCOPE="openid profile email" # default values

View File

@ -1,36 +1,51 @@
import { ProvidersInterface } from '@gitroom/backend/services/auth/providers.interface';
export class AuthentikProvider implements ProvidersInterface {
export class OauthProvider implements ProvidersInterface {
private readonly authUrl: string;
private readonly baseUrl: string;
private readonly clientId: string;
private readonly clientSecret: string;
private readonly frontendUrl: string;
private readonly tokenUrl: string;
private readonly userInfoUrl: string;
constructor() {
const {
AUTHENTIK_URL,
AUTHENTIK_CLIENT_ID,
AUTHENTIK_CLIENT_SECRET,
POSTIZ_OAUTH_AUTH_URL,
POSTIZ_OAUTH_CLIENT_ID,
POSTIZ_OAUTH_CLIENT_SECRET,
POSTIZ_OAUTH_TOKEN_URL,
POSTIZ_OAUTH_URL,
POSTIZ_OAUTH_USERINFO_URL,
FRONTEND_URL,
} = process.env;
if (!AUTHENTIK_URL)
throw new Error('AUTHENTIK_URL environment variable is not set');
if (!AUTHENTIK_CLIENT_ID)
throw new Error('AUTHENTIK_CLIENT_ID environment variable is not set');
if (!AUTHENTIK_CLIENT_SECRET)
if (!POSTIZ_OAUTH_USERINFO_URL)
throw new Error(
'AUTHENTIK_CLIENT_SECRET environment variable is not set'
'POSTIZ_OAUTH_USERINFO_URL environment variable is not set'
);
if (!POSTIZ_OAUTH_URL)
throw new Error('POSTIZ_OAUTH_URL environment variable is not set');
if (!POSTIZ_OAUTH_TOKEN_URL)
throw new Error('POSTIZ_OAUTH_TOKEN_URL environment variable is not set');
if (!POSTIZ_OAUTH_CLIENT_ID)
throw new Error('POSTIZ_OAUTH_CLIENT_ID environment variable is not set');
if (!POSTIZ_OAUTH_CLIENT_SECRET)
throw new Error(
'POSTIZ_OAUTH_CLIENT_SECRET environment variable is not set'
);
if (!POSTIZ_OAUTH_AUTH_URL)
throw new Error('POSTIZ_OAUTH_AUTH_URL environment variable is not set');
if (!FRONTEND_URL)
throw new Error('FRONTEND_URL environment variable is not set');
this.baseUrl = AUTHENTIK_URL.endsWith('/')
? AUTHENTIK_URL.slice(0, -1)
: AUTHENTIK_URL;
this.clientId = AUTHENTIK_CLIENT_ID;
this.clientSecret = AUTHENTIK_CLIENT_SECRET;
this.authUrl = POSTIZ_OAUTH_AUTH_URL;
this.baseUrl = POSTIZ_OAUTH_URL;
this.clientId = POSTIZ_OAUTH_CLIENT_ID;
this.clientSecret = POSTIZ_OAUTH_CLIENT_SECRET;
this.frontendUrl = FRONTEND_URL;
this.tokenUrl = POSTIZ_OAUTH_TOKEN_URL;
this.userInfoUrl = POSTIZ_OAUTH_USERINFO_URL;
}
generateLink(): string {
@ -41,11 +56,11 @@ export class AuthentikProvider implements ProvidersInterface {
redirect_uri: `${this.frontendUrl}/settings`,
});
return `${this.baseUrl}/application/o/authorize/?${params.toString()}`;
return `${this.authUrl}/?${params.toString()}`;
}
async getToken(code: string): Promise<string> {
const response = await fetch(`${this.baseUrl}/application/o/token/`, {
const response = await fetch(`${this.tokenUrl}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
@ -70,7 +85,7 @@ export class AuthentikProvider implements ProvidersInterface {
}
async getUser(access_token: string): Promise<{ email: string; id: string }> {
const response = await fetch(`${this.baseUrl}/application/o/userinfo/`, {
const response = await fetch(`${this.userInfoUrl}/`, {
headers: {
Authorization: `Bearer ${access_token}`,
Accept: 'application/json',

View File

@ -4,7 +4,7 @@ import { ProvidersInterface } from '@gitroom/backend/services/auth/providers.int
import { GoogleProvider } from '@gitroom/backend/services/auth/providers/google.provider';
import { FarcasterProvider } from '@gitroom/backend/services/auth/providers/farcaster.provider';
import { WalletProvider } from '@gitroom/backend/services/auth/providers/wallet.provider';
import { AuthentikProvider } from '@gitroom/backend/services/auth/providers/authentik.provider';
import { OauthProvider } from '@gitroom/backend/services/auth/providers/oauth.provider';
export class ProvidersFactory {
static loadProvider(provider: Provider): ProvidersInterface {
@ -17,8 +17,8 @@ export class ProvidersFactory {
return new FarcasterProvider();
case Provider.WALLET:
return new WalletProvider();
case Provider.AUTHENTIK:
return new AuthentikProvider();
case Provider.GENERIC:
return new OauthProvider();
}
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<path fill="#000000"
d="M504 228q-16 0 -33 1q-33 2 -70 11q-35 9 -60 20q-38 17 -77 44q-44 31 -78 75t-55 96l-5 12q-9 22 -12 33q-13 45 -13 98q0 49 9 93q22 100 82 171q52 62 139 108q48 25 109.5 33.5t124.5 -1t115 -35.5q90 -46 152 -139q31 -46 48 -100q19 -60 19 -124q0 -57 -21 -119
q-17 -52 -46 -97q-51 -81 -128 -127q-88 -53 -200 -53zM470 418h54q16 0 29 9.5t18 24.5l106 320q7 20 -2.5 38.5t-28.5 24.5q-8 2 -16 2q-16 0 -29 -9t-18 -25l-23 -73h-120l-23 73q-5 15 -18 24.5t-29 9.5q-8 0 -15 -2q-20 -6 -29.5 -24t-3.5 -38l101 -320q5 -16 18 -25.5
t29 -9.5z" />
</svg>

After

Width:  |  Height:  |  Size: 925 B

View File

@ -39,7 +39,9 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
discordUrl={process.env.NEXT_PUBLIC_DISCORD_SUPPORT!}
frontEndUrl={process.env.FRONTEND_URL!}
isGeneral={!!process.env.IS_GENERAL}
authentikOIDC={!!process.env.AUTHENTIK_OIDC}
genericOauth={!!process.env.POSTIZ_GENERIC_OAUTH}
oauthLogoUrl={process.env.NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL!}
oauthDisplayName={process.env.NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME!}
uploadDirectory={process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY!}
tolt={process.env.NEXT_PUBLIC_TOLT!}
facebookPixel={process.env.NEXT_PUBLIC_FACEBOOK_PIXEL!}

View File

@ -9,7 +9,7 @@ import { useMemo, useState } from 'react';
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
import { LoginUserDto } from '@gitroom/nestjs-libraries/dtos/auth/login.user.dto';
import { GithubProvider } from '@gitroom/frontend/components/auth/providers/github.provider';
import { AuthentikProvider } from '@gitroom/frontend/components/auth/providers/authentik.provider';
import { OauthProvider } from '@gitroom/frontend/components/auth/providers/oauth.provider';
import interClass from '@gitroom/react/helpers/inter.font';
import { GoogleProvider } from '@gitroom/frontend/components/auth/providers/google.provider';
import { useVariables } from '@gitroom/react/helpers/variable.context';
@ -25,8 +25,8 @@ type Inputs = {
export function Login() {
const [loading, setLoading] = useState(false);
const { isGeneral, neynarClientId, billingEnabled } = useVariables();
const { isGeneral, neynarClientId, authentikOIDC } = useVariables();
const { isGeneral, neynarClientId, billingEnabled, genericOauth } =
useVariables();
const resolver = useMemo(() => {
return classValidatorResolver(LoginUserDto);
}, []);
@ -65,8 +65,8 @@ export function Login() {
Sign In
</h1>
</div>
{isGeneral && authentikOIDC ? (
<AuthentikProvider />
{isGeneral && genericOauth ? (
<OauthProvider />
) : !isGeneral ? (
<GithubProvider />
) : (

View File

@ -2,13 +2,15 @@ import { useCallback } from 'react';
import Image from 'next/image';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import interClass from '@gitroom/react/helpers/inter.font';
import { useVariables } from '@gitroom/react/helpers/variable.context';
export const AuthentikProvider = () => {
export const OauthProvider = () => {
const fetch = useFetch();
const { oauthLogoUrl, oauthDisplayName } = useVariables();
const gotoLogin = useCallback(async () => {
try {
const response = await fetch('/auth/oauth/AUTHENTIK');
const response = await fetch('/auth/oauth/GENERIC');
if (!response.ok) {
throw new Error(
`Login link request failed with status ${response.status}`
@ -17,7 +19,7 @@ export const AuthentikProvider = () => {
const link = await response.text();
window.location.href = link;
} catch (error) {
console.error('Failed to get Authentik login link:', error);
console.error('Failed to get generic oauth login link:', error);
}
}, []);
@ -28,13 +30,13 @@ export const AuthentikProvider = () => {
>
<div>
<Image
src="/icons/authentik.svg"
alt="Authentik"
src={oauthLogoUrl || '/icons/generic-oauth.svg'}
alt="genericOauth"
width={40}
height={40}
/>
</div>
<div>Sign in with Authentik </div>
<div>Sign in with {oauthDisplayName || 'OAuth'}</div>
</div>
);
};

View File

@ -44,8 +44,8 @@ export async function middleware(request: NextRequest) {
? ''
: (url.indexOf('?') > -1 ? '&' : '?') +
`provider=${(findIndex === 'settings'
? process.env.AUTHENTIK_OIDC
? 'authentik'
? process.env.POSTIZ_GENERIC_OAUTH
? 'generic'
: 'github'
: findIndex
).toUpperCase()}`;

View File

@ -636,7 +636,7 @@ enum Provider {
GOOGLE
FARCASTER
WALLET
AUTHENTIK
GENERIC
}
enum Role {

View File

@ -5,7 +5,9 @@ import { createContext, FC, ReactNode, useContext, useEffect } from 'react';
interface VariableContextInterface {
billingEnabled: boolean;
isGeneral: boolean;
authentikOIDC: boolean;
genericOauth: boolean;
oauthLogoUrl: string;
oauthDisplayName: string;
frontEndUrl: string;
plontoKey: string;
storageProvider: 'local' | 'cloudflare';
@ -21,7 +23,9 @@ interface VariableContextInterface {
const VariableContext = createContext({
billingEnabled: false,
isGeneral: true,
authentikOIDC: false,
genericOauth: false,
oauthLogoUrl: '',
oauthDisplayName: '',
frontEndUrl: '',
storageProvider: 'local',
plontoKey: '',