Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
cf7916b061
16
.env.example
16
.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=""
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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!}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue