mirror of
https://github.com/diced/zipline.git
synced 2025-12-12 07:40:45 -08:00
fix: invites not working
This commit is contained in:
@@ -15,7 +15,7 @@ import {
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { notifications, showNotification } from '@mantine/notifications';
|
||||
import { IconLogin, IconPlus, IconUserPlus, IconX } from '@tabler/icons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, redirect, useLocation, useNavigate } from 'react-router-dom';
|
||||
@@ -30,7 +30,6 @@ export function Component() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [invite, setInvite] = useState<any>(null);
|
||||
|
||||
const {
|
||||
data: config,
|
||||
@@ -44,6 +43,19 @@ export function Component() {
|
||||
});
|
||||
|
||||
const code = new URLSearchParams(location.search).get('code') ?? undefined;
|
||||
const {
|
||||
data: invite,
|
||||
error: inviteError,
|
||||
isLoading: inviteLoading,
|
||||
} = useSWR<Response['/api/auth/invites/web']>(
|
||||
location.search.includes('code') ? `/api/auth/invites/web${location.search}` : null,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshWhenHidden: false,
|
||||
revalidateIfStale: false,
|
||||
},
|
||||
);
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
@@ -69,20 +81,8 @@ export function Component() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!code) return;
|
||||
if (!config) return;
|
||||
|
||||
const res = await fetch(`/api/auth/invite/web?code=${code}`);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
setInvite(json.invite);
|
||||
} else {
|
||||
redirect('/auth/login');
|
||||
}
|
||||
})();
|
||||
}, [code]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!config?.features.userRegistration) {
|
||||
navigate('/auth/login');
|
||||
}
|
||||
@@ -138,6 +138,22 @@ export function Component() {
|
||||
);
|
||||
}
|
||||
|
||||
if (code && inviteError) {
|
||||
if (inviteError) {
|
||||
showNotification({
|
||||
id: 'invalid-invite',
|
||||
message: 'Invalid or expired invite.',
|
||||
color: 'red',
|
||||
});
|
||||
|
||||
navigate('/auth/login');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inviteLoading) return <LoadingOverlay visible />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Center h='100vh'>
|
||||
{config.website.loginBackground && (
|
||||
@@ -183,8 +199,13 @@ export function Component() {
|
||||
|
||||
{invite && (
|
||||
<Text ta='center' size='sm' c='dimmed'>
|
||||
You’ve been invited to join <b>{config?.website?.title ?? 'Zipline'}</b> by{' '}
|
||||
<b>{invite.inviter?.username}</b>
|
||||
You’ve been invited to join <b>{config?.website?.title ?? 'Zipline'}</b>
|
||||
{invite.inviter && (
|
||||
<>
|
||||
{' '}
|
||||
by <b>{invite.inviter.username}</b>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiAuthInvitesResponse } from '@/server/routes/api/auth/invites';
|
||||
import { ApiAuthInvitesIdResponse } from '@/server/routes/api/auth/invites/[id]';
|
||||
import { ApiAuthInvitesWebResponse } from '@/server/routes/api/auth/invites/web';
|
||||
import { ApiLoginResponse } from '@/server/routes/api/auth/login';
|
||||
import { ApiLogoutResponse } from '@/server/routes/api/auth/logout';
|
||||
import { ApiAuthOauthResponse } from '@/server/routes/api/auth/oauth';
|
||||
@@ -45,6 +46,7 @@ import { ApiVersionResponse } from '@/server/routes/api/version';
|
||||
export type Response = {
|
||||
'/api/auth/invites/[id]': ApiAuthInvitesIdResponse;
|
||||
'/api/auth/invites': ApiAuthInvitesResponse;
|
||||
'/api/auth/invites/web': ApiAuthInvitesWebResponse;
|
||||
'/api/auth/register': ApiAuthRegisterResponse;
|
||||
'/api/auth/webauthn': ApiAuthWebauthnResponse;
|
||||
'/api/auth/oauth': ApiAuthOauthResponse;
|
||||
|
||||
@@ -1,60 +1,55 @@
|
||||
import { config } from '@/lib/config';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { Invite } from '@/lib/db/models/invite';
|
||||
import { log } from '@/lib/logger';
|
||||
import { secondlyRatelimit } from '@/lib/ratelimits';
|
||||
import { administratorMiddleware } from '@/server/middleware/administrator';
|
||||
import { userMiddleware } from '@/server/middleware/user';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
|
||||
export type ApiAuthInvitesResponse = Invite | Invite[];
|
||||
export type ApiAuthInvitesWebResponse = Invite & {
|
||||
inviter: {
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
|
||||
type Query = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
const logger = log('api').c('auth').c('invites').c('web');
|
||||
|
||||
export const PATH = '/api/auth/invites/web';
|
||||
export default fastifyPlugin(
|
||||
(server, _, done) => {
|
||||
server.get<{ Querystring: Query }>(
|
||||
PATH,
|
||||
{ preHandler: [userMiddleware, administratorMiddleware], ...secondlyRatelimit(10) },
|
||||
async (req, res) => {
|
||||
const { code } = req.query;
|
||||
server.get<{ Querystring: Query }>(PATH, secondlyRatelimit(10), async (req, res) => {
|
||||
const { code } = req.query;
|
||||
|
||||
if (!code) return res.send({ invite: null });
|
||||
if (!config.invites.enabled) return res.notFound();
|
||||
if (!code) return res.send({ invite: null });
|
||||
if (!config.invites.enabled) return res.notFound();
|
||||
|
||||
const invite = await prisma.invite.findFirst({
|
||||
where: {
|
||||
OR: [{ id: code }, { code }],
|
||||
const invite = await prisma.invite.findFirst({
|
||||
where: {
|
||||
OR: [{ id: code }, { code }],
|
||||
},
|
||||
select: {
|
||||
code: true,
|
||||
maxUses: true,
|
||||
uses: true,
|
||||
expiresAt: true,
|
||||
inviter: {
|
||||
select: { username: true },
|
||||
},
|
||||
select: {
|
||||
code: true,
|
||||
maxUses: true,
|
||||
uses: true,
|
||||
expiresAt: true,
|
||||
inviter: {
|
||||
select: { username: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!invite ||
|
||||
(invite.expiresAt && new Date(invite.expiresAt) < new Date()) ||
|
||||
(invite.maxUses && invite.uses >= invite.maxUses)
|
||||
) {
|
||||
return res.notFound();
|
||||
}
|
||||
if (
|
||||
!invite ||
|
||||
(invite.expiresAt && new Date(invite.expiresAt) < new Date()) ||
|
||||
(invite.maxUses && invite.uses >= invite.maxUses)
|
||||
) {
|
||||
return res.notFound();
|
||||
}
|
||||
|
||||
delete (invite as any).expiresAt;
|
||||
delete (invite as any).expiresAt;
|
||||
|
||||
return res.send({ invite });
|
||||
},
|
||||
);
|
||||
return res.send({ invite });
|
||||
});
|
||||
|
||||
done();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user