Merge branch 'main' into main
This commit is contained in:
commit
e888dc58c5
|
|
@ -1,9 +1,11 @@
|
|||
# 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"
|
||||
|
||||
# === This needs to be exactly the URL you're accessing Postiz on.
|
||||
FRONTEND_URL="http://localhost:4200"
|
||||
NEXT_PUBLIC_BACKEND_URL="http://localhost:3000"
|
||||
BACKEND_INTERNAL_URL="http://localhost:3000"
|
||||
|
|
@ -77,6 +79,7 @@ MASTODON_CLIENT_SECRET=""
|
|||
OPENAI_API_KEY=""
|
||||
NEXT_PUBLIC_DISCORD_SUPPORT=""
|
||||
NEXT_PUBLIC_POLOTNO=""
|
||||
NOT_SECURED=false
|
||||
|
||||
# Payment settings
|
||||
FEE_AMOUNT=0.05
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ The Postiz app is committed to ensuring the security and integrity of our users'
|
|||
If you discover a security vulnerability in the Postiz app, please report it to us privately via email to one of the maintainers:
|
||||
|
||||
* @nevo-david
|
||||
* @jamesread ([email](mailto:contact@jread.com))
|
||||
* @jonathan-irvin ([email](mailto:offendingcommit@gmail.com))
|
||||
* @egelhaus ([email](mailto:gelhausenno@outlook.de))
|
||||
|
||||
When reporting a security vulnerability, please provide as much detail as possible, including:
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export class AuthController {
|
|||
|
||||
@Get('/can-register')
|
||||
async canRegister() {
|
||||
return {register: await this._authService.canRegister()};
|
||||
return { register: await this._authService.canRegister() };
|
||||
}
|
||||
|
||||
@Post('/register')
|
||||
|
|
@ -66,20 +66,36 @@ export class AuthController {
|
|||
|
||||
response.cookie('auth', jwt, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('auth', jwt);
|
||||
}
|
||||
|
||||
if (typeof addedOrg !== 'boolean' && addedOrg?.organizationId) {
|
||||
response.cookie('showorg', addedOrg.organizationId, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('showorg', addedOrg.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
response.header('onboarding', 'true');
|
||||
|
|
@ -114,20 +130,36 @@ export class AuthController {
|
|||
|
||||
response.cookie('auth', jwt, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('auth', jwt);
|
||||
}
|
||||
|
||||
if (typeof addedOrg !== 'boolean' && addedOrg?.organizationId) {
|
||||
response.cookie('showorg', addedOrg.organizationId, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('showorg', addedOrg.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
response.header('reload', 'true');
|
||||
|
|
@ -178,12 +210,20 @@ export class AuthController {
|
|||
|
||||
response.cookie('auth', activate, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('auth', activate);
|
||||
}
|
||||
|
||||
response.header('onboarding', 'true');
|
||||
return response.status(200).send({ can: true });
|
||||
}
|
||||
|
|
@ -201,12 +241,20 @@ export class AuthController {
|
|||
|
||||
response.cookie('auth', jwt, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('auth', jwt);
|
||||
}
|
||||
|
||||
response.header('reload', 'true');
|
||||
|
||||
response.status(200).json({
|
||||
|
|
|
|||
|
|
@ -86,30 +86,37 @@ export class IntegrationsController {
|
|||
@Get('/list')
|
||||
async getIntegrationList(@GetOrgFromRequest() org: Organization) {
|
||||
return {
|
||||
integrations: (
|
||||
await this._integrationService.getIntegrationsList(org.id)
|
||||
).map((p) => {
|
||||
const findIntegration = this._integrationManager.getSocialIntegration(
|
||||
p.providerIdentifier
|
||||
);
|
||||
return {
|
||||
name: p.name,
|
||||
id: p.id,
|
||||
internalId: p.internalId,
|
||||
disabled: p.disabled,
|
||||
picture: p.picture || '/no-picture.jpg',
|
||||
identifier: p.providerIdentifier,
|
||||
inBetweenSteps: p.inBetweenSteps,
|
||||
refreshNeeded: p.refreshNeeded,
|
||||
display: p.profile,
|
||||
type: p.type,
|
||||
time: JSON.parse(p.postingTimes),
|
||||
changeProfilePicture: !!findIntegration?.changeProfilePicture,
|
||||
changeNickName: !!findIntegration?.changeNickname,
|
||||
customer: p.customer,
|
||||
additionalSettings: p.additionalSettings || '[]',
|
||||
};
|
||||
}),
|
||||
integrations: await Promise.all(
|
||||
(await this._integrationService.getIntegrationsList(org.id)).map(
|
||||
async (p) => {
|
||||
const findIntegration =
|
||||
this._integrationManager.getSocialIntegration(
|
||||
p.providerIdentifier
|
||||
);
|
||||
return {
|
||||
name: p.name,
|
||||
id: p.id,
|
||||
internalId: p.internalId,
|
||||
disabled: p.disabled,
|
||||
picture: p.picture || '/no-picture.jpg',
|
||||
identifier: p.providerIdentifier,
|
||||
inBetweenSteps: p.inBetweenSteps,
|
||||
refreshNeeded: p.refreshNeeded,
|
||||
isCustomFields: !!findIntegration.customFields,
|
||||
...(findIntegration.customFields
|
||||
? { customFields: await findIntegration.customFields() }
|
||||
: {}),
|
||||
display: p.profile,
|
||||
type: p.type,
|
||||
time: JSON.parse(p.postingTimes),
|
||||
changeProfilePicture: !!findIntegration?.changeProfilePicture,
|
||||
changeNickName: !!findIntegration?.changeNickname,
|
||||
customer: p.customer,
|
||||
additionalSettings: p.additionalSettings || '[]',
|
||||
};
|
||||
}
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -612,9 +619,7 @@ export class IntegrationsController {
|
|||
}
|
||||
|
||||
@Get('/telegram/updates')
|
||||
async getUpdates(
|
||||
@Query() query: { word: string; id?: number },
|
||||
) {
|
||||
async getUpdates(@Query() query: { word: string; id?: number }) {
|
||||
return new TelegramProvider().getBotId(query);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,8 +101,12 @@ export class PublicController {
|
|||
if (!req.cookies.track) {
|
||||
res.cookie('track', uniqueId, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
}
|
||||
: {}),
|
||||
sameSite: 'none',
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
|
@ -111,8 +115,12 @@ export class PublicController {
|
|||
if (body.fbclid && !req.cookies.fbclid) {
|
||||
res.cookie('fbclid', body.fbclid, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
}
|
||||
: {}),
|
||||
sameSite: 'none',
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,11 +48,13 @@ export class UsersController {
|
|||
async getSelf(
|
||||
@GetUserFromRequest() user: User,
|
||||
@GetOrgFromRequest() organization: Organization,
|
||||
@Req() req: Request,
|
||||
@Req() req: Request
|
||||
) {
|
||||
if (!organization) {
|
||||
throw new HttpForbiddenException();
|
||||
}
|
||||
|
||||
const impersonate = req.cookies.impersonate || req.headers.impersonate;
|
||||
// @ts-ignore
|
||||
return {
|
||||
...user,
|
||||
|
|
@ -67,12 +69,10 @@ export class UsersController {
|
|||
// @ts-ignore
|
||||
isLifetime: !!organization?.subscription?.isLifetime,
|
||||
admin: !!user.isSuperAdmin,
|
||||
impersonate: !!req.cookies.impersonate,
|
||||
impersonate: !!impersonate,
|
||||
allowTrial: organization?.allowTrial,
|
||||
// @ts-ignore
|
||||
publicApi: organization?.users[0]?.role === 'SUPERADMIN' || organization?.users[0]?.role === 'ADMIN'
|
||||
? organization?.apiKey
|
||||
: '',
|
||||
publicApi: organization?.users[0]?.role === 'SUPERADMIN' || organization?.users[0]?.role === 'ADMIN' ? organization?.apiKey : '',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -105,11 +105,19 @@ export class UsersController {
|
|||
|
||||
response.cookie('impersonate', id, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('impersonate', id);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/personal')
|
||||
|
|
@ -175,12 +183,20 @@ export class UsersController {
|
|||
) {
|
||||
response.cookie('showorg', id, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
if (process.env.NOT_SECURED) {
|
||||
response.header('showorg', id);
|
||||
}
|
||||
|
||||
response.status(200).send();
|
||||
}
|
||||
|
||||
|
|
@ -188,29 +204,41 @@ export class UsersController {
|
|||
logout(@Res({ passthrough: true }) response: Response) {
|
||||
response.cookie('auth', '', {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
maxAge: -1,
|
||||
expires: new Date(0),
|
||||
sameSite: 'none',
|
||||
});
|
||||
|
||||
response.cookie('showorg', '', {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
maxAge: -1,
|
||||
expires: new Date(0),
|
||||
sameSite: 'none',
|
||||
});
|
||||
|
||||
response.cookie('impersonate', '', {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
maxAge: -1,
|
||||
expires: new Date(0),
|
||||
sameSite: 'none',
|
||||
});
|
||||
|
||||
response.status(200).send();
|
||||
|
|
@ -223,22 +251,34 @@ export class UsersController {
|
|||
@GetUserFromRequest() user: User,
|
||||
@RealIP() ip: string,
|
||||
@UserAgent() userAgent: string,
|
||||
@Body() body: { tt: TrackEnum; fbclid: string, additional: Record<string, any> }
|
||||
@Body()
|
||||
body: { tt: TrackEnum; fbclid: string; additional: Record<string, any> }
|
||||
) {
|
||||
const uniqueId = req?.cookies?.track || makeId(10);
|
||||
const fbclid = req?.cookies?.fbclid || body.fbclid;
|
||||
await this._trackService.track(uniqueId, ip, userAgent, body.tt, body.additional, fbclid, user);
|
||||
await this._trackService.track(
|
||||
uniqueId,
|
||||
ip,
|
||||
userAgent,
|
||||
body.tt,
|
||||
body.additional,
|
||||
fbclid,
|
||||
user
|
||||
);
|
||||
if (!req.cookies.track) {
|
||||
res.cookie('track', uniqueId, {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
}
|
||||
|
||||
console.log('hello');
|
||||
res.status(200).json({
|
||||
track: uniqueId,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,8 +14,13 @@ async function bootstrap() {
|
|||
const app = await NestFactory.create(AppModule, {
|
||||
rawBody: true,
|
||||
cors: {
|
||||
credentials: true,
|
||||
exposedHeaders: ['reload', 'onboarding', 'activate'],
|
||||
...(!process.env.NOT_SECURED ? { credentials: true } : {}),
|
||||
exposedHeaders: [
|
||||
'reload',
|
||||
'onboarding',
|
||||
'activate',
|
||||
...(process.env.NOT_SECURED ? ['auth', 'showorg', 'impersonate'] : []),
|
||||
],
|
||||
origin: [
|
||||
process.env.FRONTEND_URL,
|
||||
...(process.env.MAIN_URL ? [process.env.MAIN_URL] : []),
|
||||
|
|
@ -39,8 +44,8 @@ async function bootstrap() {
|
|||
|
||||
try {
|
||||
await app.listen(port);
|
||||
|
||||
checkConfiguration() // Do this last, so that users will see obvious issues at the end of the startup log without having to scroll up.
|
||||
|
||||
checkConfiguration(); // Do this last, so that users will see obvious issues at the end of the startup log without having to scroll up.
|
||||
|
||||
Logger.log(`🚀 Backend is running on: http://localhost:${port}`);
|
||||
} catch (e) {
|
||||
|
|
@ -50,17 +55,17 @@ async function bootstrap() {
|
|||
|
||||
function checkConfiguration() {
|
||||
const checker = new ConfigurationChecker();
|
||||
checker.readEnvFromProcess()
|
||||
checker.check()
|
||||
checker.readEnvFromProcess();
|
||||
checker.check();
|
||||
|
||||
if (checker.hasIssues()) {
|
||||
for (const issue of checker.getIssues()) {
|
||||
Logger.warn(issue, 'Configuration issue')
|
||||
Logger.warn(issue, 'Configuration issue');
|
||||
}
|
||||
|
||||
Logger.warn("Configuration issues found: " + checker.getIssuesCount())
|
||||
Logger.warn('Configuration issues found: ' + checker.getIssuesCount());
|
||||
} else {
|
||||
Logger.log("Configuration check completed without any issues.")
|
||||
Logger.log('Configuration check completed without any issues.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@ import { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/excep
|
|||
export const removeAuth = (res: Response) => {
|
||||
res.cookie('auth', '', {
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(0),
|
||||
maxAge: -1,
|
||||
});
|
||||
|
|
@ -43,9 +47,10 @@ export class AuthMiddleware implements NestMiddleware {
|
|||
throw new HttpForbiddenException();
|
||||
}
|
||||
|
||||
if (user?.isSuperAdmin && req.cookies.impersonate) {
|
||||
const impersonate = req.cookies.impersonate || req.headers.impersonate;
|
||||
if (user?.isSuperAdmin && impersonate) {
|
||||
const loadImpersonate = await this._organizationService.getUserOrg(
|
||||
req.cookies.impersonate
|
||||
impersonate
|
||||
);
|
||||
|
||||
if (loadImpersonate) {
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -44,6 +44,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
|
|||
facebookPixel={process.env.NEXT_PUBLIC_FACEBOOK_PIXEL!}
|
||||
telegramBotName={process.env.TELEGRAM_BOT_NAME!}
|
||||
neynarClientId={process.env.NEYNAR_CLIENT_ID!}
|
||||
isSecured={!process.env.NOT_SECURED}
|
||||
>
|
||||
<ToltScript />
|
||||
<FacebookComponent />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import { FC, MouseEventHandler, useCallback, useMemo, useState } from 'react';
|
||||
import React, {
|
||||
FC,
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useClickOutside } from '@mantine/hooks';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
|
|
@ -11,6 +17,9 @@ import { BotPicture } from '@gitroom/frontend/components/launches/bot.picture';
|
|||
import { CustomerModal } from '@gitroom/frontend/components/launches/customer.modal';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { SettingsModal } from '@gitroom/frontend/components/launches/settings.modal';
|
||||
import { string } from 'yup';
|
||||
import { CustomVariables } from '@gitroom/frontend/components/launches/add.provider.component';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export const Menu: FC<{
|
||||
canEnable: boolean;
|
||||
|
|
@ -35,6 +44,7 @@ export const Menu: FC<{
|
|||
refreshChannel,
|
||||
} = props;
|
||||
const fetch = useFetch();
|
||||
const router = useRouter();
|
||||
const { integrations } = useCalendar();
|
||||
const toast = useToaster();
|
||||
const modal = useModals();
|
||||
|
|
@ -209,6 +219,23 @@ export const Menu: FC<{
|
|||
setShow(false);
|
||||
}, [integrations]);
|
||||
|
||||
const updateCredentials = useCallback(() => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
children: (
|
||||
<CustomVariables
|
||||
identifier={findIntegration.identifier}
|
||||
gotoUrl={(url: string) => router.push(url)}
|
||||
variables={findIntegration.customFields}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="cursor-pointer relative select-none"
|
||||
|
|
@ -232,10 +259,34 @@ export const Menu: FC<{
|
|||
onClick={(e) => e.stopPropagation()}
|
||||
className={`absolute top-[100%] left-0 p-[8px] px-[20px] bg-fifth flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder ${interClass} text-nowrap`}
|
||||
>
|
||||
{canDisable && findIntegration?.refreshNeeded && (
|
||||
{canDisable &&
|
||||
findIntegration?.refreshNeeded &&
|
||||
!findIntegration.customFields && (
|
||||
<div
|
||||
className="flex gap-[12px] items-center"
|
||||
onClick={refreshChannel(findIntegration!)}
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
width={18}
|
||||
height={18}
|
||||
viewBox="0 0 32 32"
|
||||
fill="yellow"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.00079 15.9999C3.00343 13.6138 3.95249 11.3262 5.63975 9.63891C7.327 7.95165 9.61465 7.00259 12.0008 6.99995H25.587L24.2933 5.70745C24.1056 5.5198 24.0002 5.26531 24.0002 4.99995C24.0002 4.73458 24.1056 4.48009 24.2933 4.29245C24.4809 4.1048 24.7354 3.99939 25.0008 3.99939C25.2661 3.99939 25.5206 4.10481 25.7083 4.29245L28.7083 7.29245C28.8013 7.38532 28.875 7.49561 28.9253 7.61701C28.9757 7.7384 29.0016 7.86853 29.0016 7.99995C29.0016 8.13136 28.9757 8.26149 28.9253 8.38289C28.875 8.50428 28.8013 8.61457 28.7083 8.70745L25.7083 11.7074C25.5206 11.8951 25.2661 12.0005 25.0008 12.0005C24.7354 12.0005 24.4809 11.8951 24.2933 11.7074C24.1056 11.5198 24.0002 11.2653 24.0002 10.9999C24.0002 10.7346 24.1056 10.4801 24.2933 10.2924L25.587 8.99995H12.0008C10.1449 9.00193 8.36556 9.74007 7.05323 11.0524C5.74091 12.3647 5.00277 14.144 5.00079 15.9999C5.00079 16.2652 4.89543 16.5195 4.70789 16.7071C4.52036 16.8946 4.266 16.9999 4.00079 16.9999C3.73557 16.9999 3.48122 16.8946 3.29368 16.7071C3.10614 16.5195 3.00079 16.2652 3.00079 15.9999ZM28.0008 14.9999C27.7356 14.9999 27.4812 15.1053 27.2937 15.2928C27.1061 15.4804 27.0008 15.7347 27.0008 15.9999C26.9988 17.8559 26.2607 19.6352 24.9483 20.9475C23.636 22.2598 21.8567 22.998 20.0008 22.9999H6.41454L7.70829 21.7074C7.8012 21.6145 7.8749 21.5042 7.92518 21.3828C7.97546 21.2614 8.00134 21.1313 8.00134 20.9999C8.00134 20.8686 7.97546 20.7384 7.92518 20.6171C7.8749 20.4957 7.8012 20.3854 7.70829 20.2924C7.61538 20.1995 7.50508 20.1258 7.38368 20.0756C7.26229 20.0253 7.13218 19.9994 7.00079 19.9994C6.86939 19.9994 6.73928 20.0253 6.61789 20.0756C6.4965 20.1258 6.3862 20.1995 6.29329 20.2924L3.29329 23.2924C3.20031 23.3853 3.12655 23.4956 3.07623 23.617C3.0259 23.7384 3 23.8685 3 23.9999C3 24.1314 3.0259 24.2615 3.07623 24.3829C3.12655 24.5043 3.20031 24.6146 3.29329 24.7074L6.29329 27.7074C6.3862 27.8004 6.4965 27.8741 6.61789 27.9243C6.73928 27.9746 6.86939 28.0005 7.00079 28.0005C7.13218 28.0005 7.26229 27.9746 7.38368 27.9243C7.50508 27.8741 7.61538 27.8004 7.70829 27.7074C7.8012 27.6145 7.8749 27.5042 7.92518 27.3828C7.97546 27.2614 8.00134 27.1313 8.00134 26.9999C8.00134 26.8686 7.97546 26.7384 7.92518 26.6171C7.8749 26.4957 7.8012 26.3854 7.70829 26.2924L6.41454 24.9999H20.0008C22.3869 24.9973 24.6746 24.0482 26.3618 22.361C28.0491 20.6737 28.9981 18.3861 29.0008 15.9999C29.0008 15.7347 28.8954 15.4804 28.7079 15.2928C28.5204 15.1053 28.266 14.9999 28.0008 14.9999Z"
|
||||
fill="yellow"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px]">Reconnect channel</div>
|
||||
</div>
|
||||
)}
|
||||
{!!findIntegration?.isCustomFields && (
|
||||
<div
|
||||
className="flex gap-[12px] items-center"
|
||||
onClick={refreshChannel(findIntegration!)}
|
||||
onClick={updateCredentials}
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
|
|
@ -251,7 +302,7 @@ export const Menu: FC<{
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px]">Reconnect channel</div>
|
||||
<div className="text-[12px]">Update Credentials</div>
|
||||
</div>
|
||||
)}
|
||||
{findIntegration?.additionalSettings !== '[]' && (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
|
||||
|
||||
export default withProvider(
|
||||
null,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => {
|
||||
return true;
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
|
|
@ -20,6 +20,7 @@ import BlueskyProvider from '@gitroom/frontend/components/launches/providers/blu
|
|||
import LemmyProvider from '@gitroom/frontend/components/launches/providers/lemmy/lemmy.provider';
|
||||
import WarpcastProvider from '@gitroom/frontend/components/launches/providers/warpcast/warpcast.provider';
|
||||
import TelegramProvider from '@gitroom/frontend/components/launches/providers/telegram/telegram.provider';
|
||||
import NostrProvider from '@gitroom/frontend/components/launches/providers/nostr/nostr.provider';
|
||||
|
||||
export const Providers = [
|
||||
{identifier: 'devto', component: DevtoProvider},
|
||||
|
|
@ -44,6 +45,7 @@ export const Providers = [
|
|||
{identifier: 'lemmy', component: LemmyProvider},
|
||||
{identifier: 'wrapcast', component: WarpcastProvider},
|
||||
{identifier: 'telegram', component: TelegramProvider},
|
||||
{identifier: 'nostr', component: NostrProvider},
|
||||
];
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
'use client';
|
||||
import '@neynar/react/dist/style.css';
|
||||
import React, { FC, useMemo, useState, useCallback, useEffect } from 'react';
|
||||
import { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import {
|
||||
NeynarAuthButton,
|
||||
NeynarContextProvider,
|
||||
Theme,
|
||||
useNeynarContext,
|
||||
} from '@neynar/react';
|
||||
import { INeynarAuthenticatedUser } from '@neynar/react/dist/types/common';
|
||||
import { ButtonCaster } from '@gitroom/frontend/components/auth/providers/farcaster.provider';
|
||||
|
||||
export const WrapcasterProvider: FC<Web3ProviderInterface> = (props) => {
|
||||
const [_, state] = props.nonce.split('||');
|
||||
const modal = useModals();
|
||||
const [hide, setHide] = useState(false);
|
||||
|
||||
const auth = useCallback((code: string) => {
|
||||
setHide(true);
|
||||
return props.onComplete(code, state);
|
||||
}, [state]);
|
||||
|
||||
return (
|
||||
<div className="rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative w-full">
|
||||
<TopTitle title={`Add Wrapcast`} />
|
||||
<button
|
||||
className="outline-none absolute right-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
onClick={() => modal.closeAll()}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div className="justify-center items-center flex">
|
||||
{hide ? (
|
||||
<div className="justify-center items-center flex -mt-[90px]">
|
||||
<LoadingComponent width={100} height={100} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="justify-center items-center py-[20px] flex-col w-[500px]">
|
||||
<ButtonCaster login={auth} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -6,6 +6,8 @@ import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
|||
import { Select } from '@gitroom/react/form/select';
|
||||
import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { setCookie } from '@gitroom/frontend/components/layout/layout.context';
|
||||
|
||||
export const Subscription = () => {
|
||||
const fetch = useFetch();
|
||||
|
|
@ -53,6 +55,7 @@ export const Subscription = () => {
|
|||
export const Impersonate = () => {
|
||||
const fetch = useFetch();
|
||||
const [name, setName] = useState('');
|
||||
const { isSecured } = useVariables();
|
||||
const user = useUser();
|
||||
|
||||
const load = useCallback(async () => {
|
||||
|
|
@ -65,10 +68,14 @@ export const Impersonate = () => {
|
|||
}, [name]);
|
||||
|
||||
const stopImpersonating = useCallback(async () => {
|
||||
await fetch(`/user/impersonate`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id: '' }),
|
||||
});
|
||||
if (!isSecured) {
|
||||
setCookie('impersonate', '', -10);
|
||||
} else {
|
||||
await fetch(`/user/impersonate`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id: '' }),
|
||||
});
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -14,13 +14,57 @@ export default function LayoutContext(params: { children: ReactNode }) {
|
|||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export function setCookie(cname: string, cvalue: string, exdays: number) {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
const expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/';
|
||||
}
|
||||
|
||||
function LayoutContextInner(params: { children: ReactNode }) {
|
||||
const returnUrl = useReturnUrl();
|
||||
const {backendUrl, isGeneral} = useVariables();
|
||||
const { backendUrl, isGeneral, isSecured } = useVariables();
|
||||
|
||||
const afterRequest = useCallback(
|
||||
async (url: string, options: RequestInit, response: Response) => {
|
||||
if (typeof window !== 'undefined' && window.location.href.includes('/p/')) {
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
window.location.href.includes('/p/')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const headerAuth =
|
||||
response?.headers?.get('auth') || response?.headers?.get('Auth');
|
||||
const showOrg =
|
||||
response?.headers?.get('showorg') || response?.headers?.get('Showorg');
|
||||
const impersonate =
|
||||
response?.headers?.get('impersonate') ||
|
||||
response?.headers?.get('Impersonate');
|
||||
const logout =
|
||||
response?.headers?.get('logout') || response?.headers?.get('Logout');
|
||||
|
||||
if (headerAuth) {
|
||||
setCookie('auth', headerAuth, 365);
|
||||
}
|
||||
|
||||
if (showOrg) {
|
||||
setCookie('showorg', showOrg, 365);
|
||||
}
|
||||
|
||||
if (impersonate) {
|
||||
setCookie('impersonate', impersonate, 365);
|
||||
}
|
||||
|
||||
if (logout && !isSecured) {
|
||||
setCookie('auth', '', -10);
|
||||
setCookie('showorg', '', -10);
|
||||
setCookie('impersonate', '', -10);
|
||||
window.location.href = '/';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -50,6 +94,11 @@ function LayoutContextInner(params: { children: ReactNode }) {
|
|||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
if (!isSecured) {
|
||||
setCookie('auth', '', -10);
|
||||
setCookie('showorg', '', -10);
|
||||
setCookie('impersonate', '', -10);
|
||||
}
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
|
|
@ -74,10 +123,7 @@ function LayoutContextInner(params: { children: ReactNode }) {
|
|||
);
|
||||
|
||||
return (
|
||||
<FetchWrapperComponent
|
||||
baseUrl={backendUrl}
|
||||
afterRequest={afterRequest}
|
||||
>
|
||||
<FetchWrapperComponent baseUrl={backendUrl} afterRequest={afterRequest}>
|
||||
{params?.children || <></>}
|
||||
</FetchWrapperComponent>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,19 +4,28 @@ import { useCallback } from 'react';
|
|||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { setCookie } from '@gitroom/frontend/components/layout/layout.context';
|
||||
|
||||
export const LogoutComponent = () => {
|
||||
const fetch = useFetch();
|
||||
const {isGeneral} = useVariables();
|
||||
const { isGeneral, isSecured } = useVariables();
|
||||
const logout = useCallback(async () => {
|
||||
if (await deleteDialog('Are you sure you want to logout?', 'Yes logout')) {
|
||||
await fetch('/user/logout', {
|
||||
method: 'POST',
|
||||
});
|
||||
if (!isSecured) {
|
||||
setCookie('auth', '', -10);
|
||||
} else {
|
||||
await fetch('/user/logout', {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
window.location.href = '/';
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <div className="text-red-400 cursor-pointer" onClick={logout}>Logout from {isGeneral ? 'Postiz' : 'Gitroom'}</div>;
|
||||
return (
|
||||
<div className="text-red-400 cursor-pointer" onClick={logout}>
|
||||
Logout from {isGeneral ? 'Postiz' : 'Gitroom'}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
|
|||
export async function middleware(request: NextRequest) {
|
||||
const nextUrl = request.nextUrl;
|
||||
const authCookie = request.cookies.get('auth');
|
||||
if (nextUrl.pathname.startsWith('/uploads/') || nextUrl.pathname.startsWith('/p/') || nextUrl.pathname.startsWith('/icons/')) {
|
||||
if (
|
||||
nextUrl.pathname.startsWith('/uploads/') ||
|
||||
nextUrl.pathname.startsWith('/p/') ||
|
||||
nextUrl.pathname.startsWith('/icons/')
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
// If the URL is logout, delete the cookie and redirect to login
|
||||
|
|
@ -17,9 +21,13 @@ export async function middleware(request: NextRequest) {
|
|||
);
|
||||
response.cookies.set('auth', '', {
|
||||
path: '/',
|
||||
sameSite: false,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: false,
|
||||
}
|
||||
: {}),
|
||||
maxAge: -1,
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
});
|
||||
|
|
@ -53,12 +61,16 @@ export async function middleware(request: NextRequest) {
|
|||
if (org) {
|
||||
const redirect = NextResponse.redirect(new URL(`/`, nextUrl.href));
|
||||
redirect.cookies.set('org', org, {
|
||||
path: '/',
|
||||
sameSite: false,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
path: '/',
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: false,
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 15 * 60 * 1000),
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
});
|
||||
return redirect;
|
||||
}
|
||||
|
|
@ -81,12 +93,16 @@ export async function middleware(request: NextRequest) {
|
|||
);
|
||||
if (id) {
|
||||
redirect.cookies.set('showorg', id, {
|
||||
path: '/',
|
||||
sameSite: false,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
path: '/',
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: false,
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 15 * 60 * 1000),
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -112,9 +128,13 @@ export async function middleware(request: NextRequest) {
|
|||
|
||||
next.cookies.set('marketplace', type === 'seller' ? 'seller' : 'buyer', {
|
||||
path: '/',
|
||||
sameSite: false,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
...(!process.env.NOT_SECURED
|
||||
? {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: false,
|
||||
}
|
||||
: {}),
|
||||
expires: new Date(Date.now() + 15 * 60 * 1000),
|
||||
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
|
||||
});
|
||||
|
|
@ -122,6 +142,7 @@ export async function middleware(request: NextRequest) {
|
|||
|
||||
return next;
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
return NextResponse.redirect(new URL('/auth/logout', nextUrl.href));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
export interface Params {
|
||||
baseUrl: string;
|
||||
beforeRequest?: (url: string, options: RequestInit) => Promise<RequestInit>;
|
||||
|
|
@ -11,21 +10,48 @@ export interface Params {
|
|||
export const customFetch = (
|
||||
params: Params,
|
||||
auth?: string,
|
||||
showorg?: string
|
||||
showorg?: string,
|
||||
secured: boolean = true
|
||||
) => {
|
||||
return async function newFetch(url: string, options: RequestInit = {}) {
|
||||
const newRequestObject = await params?.beforeRequest?.(url, options);
|
||||
const authNonSecuredCookie = typeof document === 'undefined' ? null : document.cookie
|
||||
.split(';')
|
||||
.find((p) => p.includes('auth='))
|
||||
?.split('=')[1];
|
||||
|
||||
const authNonSecuredOrg = typeof document === 'undefined' ? null : document.cookie
|
||||
.split(';')
|
||||
.find((p) => p.includes('showorg='))
|
||||
?.split('=')[1];
|
||||
|
||||
const authNonSecuredImpersonate = typeof document === 'undefined' ? null : document.cookie
|
||||
.split(';')
|
||||
.find((p) => p.includes('impersonate='))
|
||||
?.split('=')[1];
|
||||
|
||||
const fetchRequest = await fetch(params.baseUrl + url, {
|
||||
credentials: 'include',
|
||||
...(secured ? { credentials: 'include' } : {}),
|
||||
...(newRequestObject || options),
|
||||
headers: {
|
||||
...(auth ? { auth } : {}),
|
||||
...(showorg ? { showorg } : {}),
|
||||
...(showorg
|
||||
? { showorg }
|
||||
: authNonSecuredOrg
|
||||
? { showorg: authNonSecuredOrg }
|
||||
: {}),
|
||||
...(options.body instanceof FormData
|
||||
? {}
|
||||
: { 'Content-Type': 'application/json' }),
|
||||
Accept: 'application/json',
|
||||
...options?.headers,
|
||||
...(auth
|
||||
? { auth }
|
||||
: authNonSecuredCookie
|
||||
? { auth: authNonSecuredCookie }
|
||||
: {}),
|
||||
...(authNonSecuredImpersonate
|
||||
? { impersonate: authNonSecuredImpersonate }
|
||||
: {}),
|
||||
},
|
||||
// @ts-ignore
|
||||
...(!options.next && options.cache !== 'force-cache'
|
||||
|
|
|
|||
|
|
@ -1,30 +1,46 @@
|
|||
"use client";
|
||||
'use client';
|
||||
|
||||
import {createContext, FC, ReactNode, useContext, useRef, useState} from "react";
|
||||
import {customFetch, Params} from "./custom.fetch.func";
|
||||
import {
|
||||
createContext,
|
||||
FC,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { customFetch, Params } from './custom.fetch.func';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
|
||||
const FetchProvider = createContext(customFetch(
|
||||
const FetchProvider = createContext(
|
||||
customFetch(
|
||||
// @ts-ignore
|
||||
{
|
||||
baseUrl: '',
|
||||
beforeRequest: () => {},
|
||||
afterRequest: () => {
|
||||
return true;
|
||||
}
|
||||
} as Params));
|
||||
baseUrl: '',
|
||||
beforeRequest: () => {},
|
||||
afterRequest: () => {
|
||||
return true;
|
||||
},
|
||||
} as Params
|
||||
)
|
||||
);
|
||||
|
||||
export const FetchWrapperComponent: FC<Params & {children: ReactNode}> = (props) => {
|
||||
const {children, ...params} = props;
|
||||
export const FetchWrapperComponent: FC<Params & { children: ReactNode }> = (
|
||||
props
|
||||
) => {
|
||||
const { children, ...params } = props;
|
||||
const { isSecured } = useVariables();
|
||||
// @ts-ignore
|
||||
const fetchData = useRef(
|
||||
customFetch(params, undefined, undefined, isSecured)
|
||||
);
|
||||
return (
|
||||
// @ts-ignore
|
||||
const fetchData = useRef(customFetch(params));
|
||||
return (
|
||||
// @ts-ignore
|
||||
<FetchProvider.Provider value={fetchData.current}>
|
||||
{children}
|
||||
</FetchProvider.Provider>
|
||||
)
|
||||
}
|
||||
<FetchProvider.Provider value={fetchData.current}>
|
||||
{children}
|
||||
</FetchProvider.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useFetch = () => {
|
||||
return useContext(FetchProvider);
|
||||
}
|
||||
return useContext(FetchProvider);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -170,6 +170,10 @@ export class IntegrationRepository {
|
|||
: '[]',
|
||||
},
|
||||
update: {
|
||||
...(additionalSettings
|
||||
? { additionalSettings: JSON.stringify(additionalSettings) }
|
||||
: {}),
|
||||
...(customInstanceDetails ? { customInstanceDetails } : {}),
|
||||
type: type as any,
|
||||
...(!refresh
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { LemmyProvider } from '@gitroom/nestjs-libraries/integrations/social/lem
|
|||
import { InstagramStandaloneProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.standalone.provider';
|
||||
import { FarcasterProvider } from '@gitroom/nestjs-libraries/integrations/social/farcaster.provider';
|
||||
import { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider';
|
||||
import { NostrProvider } from '@gitroom/nestjs-libraries/integrations/social/nostr.provider';
|
||||
|
||||
const socialIntegrationList: SocialProvider[] = [
|
||||
new XProvider(),
|
||||
|
|
@ -46,6 +47,7 @@ const socialIntegrationList: SocialProvider[] = [
|
|||
new LemmyProvider(),
|
||||
new FarcasterProvider(),
|
||||
new TelegramProvider(),
|
||||
new NostrProvider(),
|
||||
// new MastodonCustomProvider(),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import {
|
|||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import {
|
||||
NotEnoughScopes,
|
||||
SocialAbstract,
|
||||
NotEnoughScopes, RefreshToken, SocialAbstract
|
||||
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { BskyAgent, RichText } from '@atproto/api';
|
||||
import dayjs from 'dayjs';
|
||||
|
|
@ -69,7 +68,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
|
|||
{
|
||||
key: 'identifier',
|
||||
label: 'Identifier',
|
||||
validation: `/^.{3,}$/`,
|
||||
validation: `/^.+$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
|
|
@ -152,10 +151,14 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
|
|||
service: body.service,
|
||||
});
|
||||
|
||||
await agent.login({
|
||||
identifier: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
try {
|
||||
await agent.login({
|
||||
identifier: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new RefreshToken('bluesky', JSON.stringify(err), {} as BodyInit);
|
||||
}
|
||||
|
||||
let loadCid = '';
|
||||
let loadUri = '';
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ export class DiscordProvider extends SocialAbstract implements SocialProvider {
|
|||
).json();
|
||||
|
||||
return list
|
||||
.filter((p: any) => p.type === 0 || p.type === 15)
|
||||
.filter((p: any) => p.type === 0 || p.type === 5 || p.type === 15)
|
||||
.map((p: any) => ({
|
||||
id: String(p.id),
|
||||
name: p.name,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,196 @@
|
|||
import {
|
||||
AuthTokenDetails,
|
||||
PostDetails,
|
||||
PostResponse,
|
||||
SocialProvider,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import dayjs from 'dayjs';
|
||||
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { getPublicKey, Relay, finalizeEvent } from 'nostr-tools';
|
||||
import WebSocket from 'ws';
|
||||
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
||||
|
||||
// @ts-ignore
|
||||
global.WebSocket = WebSocket;
|
||||
|
||||
const list = [
|
||||
'wss://relay.primal.net',
|
||||
'wss://relay.damus.io',
|
||||
'wss://relay.snort.social',
|
||||
'wss://nostr.wine',
|
||||
'wss://nos.lol',
|
||||
'wss://relay.primal.net',
|
||||
];
|
||||
|
||||
export class NostrProvider extends SocialAbstract implements SocialProvider {
|
||||
identifier = 'nostr';
|
||||
name = 'Nostr';
|
||||
isBetweenSteps = false;
|
||||
scopes = [];
|
||||
|
||||
async customFields() {
|
||||
return [
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Nostr private key',
|
||||
validation: `/^.{3,}$/`,
|
||||
type: 'password' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
|
||||
return {
|
||||
refreshToken: '',
|
||||
expiresIn: 0,
|
||||
accessToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
picture: '',
|
||||
username: '',
|
||||
};
|
||||
}
|
||||
|
||||
async generateAuthUrl() {
|
||||
const state = makeId(17);
|
||||
return {
|
||||
url: '',
|
||||
codeVerifier: makeId(10),
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
private async findRelayInformation(pubkey: string) {
|
||||
for (const relay of list) {
|
||||
const relayInstance = await Relay.connect(relay);
|
||||
const value = await new Promise<any>((resolve) => {
|
||||
console.log('connecting');
|
||||
relayInstance.subscribe([{ kinds: [0], authors: [pubkey] }], {
|
||||
eoseTimeout: 6000,
|
||||
onevent: (event) => {
|
||||
resolve(event);
|
||||
},
|
||||
oneose: () => {
|
||||
resolve({});
|
||||
},
|
||||
onclose: () => {
|
||||
resolve({});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
relayInstance.close();
|
||||
const content = JSON.parse(value?.content || '{}');
|
||||
if (content.name || content.displayName || content.display_name) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private async publish(pubkey: string, event: any) {
|
||||
let id = '';
|
||||
for (const relay of list) {
|
||||
try {
|
||||
const relayInstance = await Relay.connect(relay);
|
||||
const value = new Promise<any>((resolve) => {
|
||||
relayInstance.subscribe([{ kinds: [1], authors: [pubkey] }], {
|
||||
eoseTimeout: 6000,
|
||||
onevent: (event) => {
|
||||
resolve(event);
|
||||
},
|
||||
oneose: () => {
|
||||
resolve({});
|
||||
},
|
||||
onclose: () => {
|
||||
resolve({});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await relayInstance.publish(event);
|
||||
const all = await value;
|
||||
relayInstance.close();
|
||||
// relayInstance.close();
|
||||
id = id || all?.id;
|
||||
} catch (err) {
|
||||
/**empty**/
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async authenticate(params: {
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
refresh?: string;
|
||||
}) {
|
||||
try {
|
||||
const body = JSON.parse(Buffer.from(params.code, 'base64').toString());
|
||||
|
||||
const pubkey = getPublicKey(
|
||||
Uint8Array.from(
|
||||
body.password.match(/.{1,2}/g).map((byte: any) => parseInt(byte, 16))
|
||||
)
|
||||
);
|
||||
|
||||
const user = await this.findRelayInformation(pubkey);
|
||||
|
||||
return {
|
||||
id: String(user.pubkey),
|
||||
name: user.display_name || user.displayName || 'No Name',
|
||||
accessToken: AuthService.signJWT({ password: body.password }),
|
||||
refreshToken: '',
|
||||
expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(),
|
||||
picture: user.picture,
|
||||
username: user.name || 'nousername',
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return 'Invalid credentials';
|
||||
}
|
||||
}
|
||||
|
||||
async post(
|
||||
id: string,
|
||||
accessToken: string,
|
||||
postDetails: PostDetails[]
|
||||
): Promise<PostResponse[]> {
|
||||
const { password } = AuthService.verifyJWT(accessToken) as any;
|
||||
|
||||
let lastId = '';
|
||||
const ids: PostResponse[] = [];
|
||||
for (const post of postDetails) {
|
||||
const textEvent = finalizeEvent(
|
||||
{
|
||||
kind: 1, // Text note
|
||||
content:
|
||||
post.message + '\n\n' + post.media?.map((m) => m.url).join('\n\n'),
|
||||
tags: [
|
||||
...(lastId
|
||||
? [
|
||||
['e', lastId, '', 'reply'],
|
||||
['p', id],
|
||||
]
|
||||
: []),
|
||||
], // Include delegation token in the event
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
},
|
||||
password
|
||||
);
|
||||
|
||||
lastId = await this.publish(id, textEvent);
|
||||
ids.push({
|
||||
id: post.id,
|
||||
postId: String(lastId),
|
||||
releaseURL: `https://primal.net/e/${lastId}`,
|
||||
status: 'completed',
|
||||
});
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ interface VariableContextInterface {
|
|||
facebookPixel: string;
|
||||
telegramBotName: string;
|
||||
neynarClientId: string;
|
||||
isSecured: boolean;
|
||||
tolt: string;
|
||||
}
|
||||
const VariableContext = createContext({
|
||||
|
|
@ -25,6 +26,7 @@ const VariableContext = createContext({
|
|||
backendUrl: '',
|
||||
discordUrl: '',
|
||||
uploadDirectory: '',
|
||||
isSecured: false,
|
||||
telegramBotName: '',
|
||||
facebookPixel: '',
|
||||
neynarClientId: '',
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -37,9 +37,9 @@
|
|||
"@aws-sdk/client-s3": "3.712.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.712.0",
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@copilotkit/react-core": "^1.5.11",
|
||||
"@copilotkit/react-textarea": "^1.5.11",
|
||||
"@copilotkit/react-ui": "^1.5.11",
|
||||
"@copilotkit/react-core": "^1.5.13",
|
||||
"@copilotkit/react-textarea": "^1.5.13",
|
||||
"@copilotkit/react-ui": "^1.5.13",
|
||||
"@copilotkit/runtime": "^1.4.7",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@langchain/community": "^0.3.19",
|
||||
|
|
@ -135,6 +135,7 @@
|
|||
"next-plausible": "^3.12.0",
|
||||
"node-telegram-bot-api": "^0.66.0",
|
||||
"nodemailer": "^6.9.15",
|
||||
"nostr-tools": "^2.10.4",
|
||||
"nx": "19.7.2",
|
||||
"openai": "^4.47.1",
|
||||
"polotno": "^2.10.5",
|
||||
|
|
@ -171,6 +172,7 @@
|
|||
"utf-8-validate": "^5.0.10",
|
||||
"uuid": "^10.0.0",
|
||||
"viem": "^2.22.9",
|
||||
"ws": "^8.18.0",
|
||||
"yargs": "^17.7.2",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "^3.24.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue