feat(oidc): use generic implementation
This commit is contained in:
parent
f9aa278883
commit
17892e64ad
21
.env.example
21
.env.example
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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!}
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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()}`;
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ enum Provider {
|
|||
GOOGLE
|
||||
FARCASTER
|
||||
WALLET
|
||||
AUTHENTIK
|
||||
GENERIC
|
||||
}
|
||||
|
||||
enum Role {
|
||||
|
|
|
|||
|
|
@ -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: '',
|
||||
|
|
|
|||
Loading…
Reference in New Issue