Merge remote-tracking branch 'origin/main'

This commit is contained in:
Nevo David 2025-05-05 20:38:37 +07:00
commit cf7916b061
10 changed files with 192 additions and 12 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=""
@ -92,3 +90,13 @@ STRIPE_SIGNING_KEY_CONNECT=""
# Developer Settings
NX_ADD_PLUGINS=false
IS_GENERAL="true" # required for now
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

@ -0,0 +1,103 @@
import { ProvidersInterface } from '@gitroom/backend/services/auth/providers.interface';
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 {
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 (!POSTIZ_OAUTH_USERINFO_URL)
throw new Error(
'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.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 {
const params = new URLSearchParams({
client_id: this.clientId,
scope: 'openid profile email',
response_type: 'code',
redirect_uri: `${this.frontendUrl}/settings`,
});
return `${this.authUrl}/?${params.toString()}`;
}
async getToken(code: string): Promise<string> {
const response = await fetch(`${this.tokenUrl}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: this.clientId,
client_secret: this.clientSecret,
code,
redirect_uri: `${this.frontendUrl}/settings`,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token request failed: ${error}`);
}
const { access_token } = await response.json();
return access_token;
}
async getUser(access_token: string): Promise<{ email: string; id: string }> {
const response = await fetch(`${this.userInfoUrl}/`, {
headers: {
Authorization: `Bearer ${access_token}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const error = await response.text();
throw new Error(`User info request failed: ${error}`);
}
const { email, sub: id } = await response.json();
return { email, id };
}
}

View File

@ -4,6 +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 { OauthProvider } from '@gitroom/backend/services/auth/providers/oauth.provider';
export class ProvidersFactory {
static loadProvider(provider: Provider): ProvidersInterface {
@ -16,6 +17,8 @@ export class ProvidersFactory {
return new FarcasterProvider();
case Provider.WALLET:
return new WalletProvider();
case Provider.GENERIC:
return new OauthProvider();
}
}
}

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,6 +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}
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,6 +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 { 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';
@ -24,7 +25,8 @@ type Inputs = {
export function Login() {
const [loading, setLoading] = useState(false);
const { isGeneral, neynarClientId, billingEnabled } = useVariables();
const { isGeneral, neynarClientId, billingEnabled, genericOauth } =
useVariables();
const resolver = useMemo(() => {
return classValidatorResolver(LoginUserDto);
}, []);
@ -63,8 +65,9 @@ export function Login() {
Sign In
</h1>
</div>
{!isGeneral ? (
{isGeneral && genericOauth ? (
<OauthProvider />
) : !isGeneral ? (
<GithubProvider />
) : (
<div className="gap-[5px] flex flex-col">

View File

@ -0,0 +1,42 @@
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 OauthProvider = () => {
const fetch = useFetch();
const { oauthLogoUrl, oauthDisplayName } = useVariables();
const gotoLogin = useCallback(async () => {
try {
const response = await fetch('/auth/oauth/GENERIC');
if (!response.ok) {
throw new Error(
`Login link request failed with status ${response.status}`
);
}
const link = await response.text();
window.location.href = link;
} catch (error) {
console.error('Failed to get generic oauth login link:', error);
}
}, []);
return (
<div
onClick={gotoLogin}
className={`cursor-pointer bg-white h-[44px] rounded-[4px] flex justify-center items-center text-customColor16 ${interClass} gap-[4px]`}
>
<div>
<Image
src={oauthLogoUrl || '/icons/generic-oauth.svg'}
alt="genericOauth"
width={40}
height={40}
/>
</div>
<div>Sign in with {oauthDisplayName || 'OAuth'}</div>
</div>
);
};

View File

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

View File

@ -636,6 +636,7 @@ enum Provider {
GOOGLE
FARCASTER
WALLET
GENERIC
}
enum Role {
@ -648,4 +649,4 @@ enum APPROVED_SUBMIT_FOR_ORDER {
NO
WAITING_CONFIRMATION
YES
}
}

View File

@ -5,9 +5,12 @@ import { createContext, FC, ReactNode, useContext, useEffect } from 'react';
interface VariableContextInterface {
billingEnabled: boolean;
isGeneral: boolean;
genericOauth: boolean;
oauthLogoUrl: string;
oauthDisplayName: string;
frontEndUrl: string;
plontoKey: string;
storageProvider: 'local' | 'cloudflare',
storageProvider: 'local' | 'cloudflare';
backendUrl: string;
discordUrl: string;
uploadDirectory: string;
@ -20,6 +23,9 @@ interface VariableContextInterface {
const VariableContext = createContext({
billingEnabled: false,
isGeneral: true,
genericOauth: false,
oauthLogoUrl: '',
oauthDisplayName: '',
frontEndUrl: '',
storageProvider: 'local',
plontoKey: '',
@ -52,9 +58,9 @@ export const VariableContextComponent: FC<
export const useVariables = () => {
return useContext(VariableContext);
}
};
export const loadVars = () => {
// @ts-ignore
return window.vars as VariableContextInterface;
}
};