mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Port auth
This commit is contained in:
parent
3007c6aa38
commit
4dbc56e98c
2
.env
2
.env
|
|
@ -5,7 +5,7 @@ DATABASE_URL=postgresql://sendou@localhost:5432/sendou_ink_trpc?schema=public
|
|||
// you can get them by making an application on https://discord.com/developers
|
||||
DISCORD_CLIENT_ID=581483359159582722
|
||||
DISCORD_CLIENT_SECRET=qxFxBlALcRsqUG2m7WYLHJ8rzjbddBTx
|
||||
DISCORD_CALLBACK_URL=http://localhost:3001/auth/discord/callback
|
||||
DISCORD_CALLBACK_URL=http://localhost:3000/auth/discord/callback
|
||||
COOKIE_SECRET=secret
|
||||
FRONTEND_URL=http://localhost:3000
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,5 @@ DATABASE_URL=postgresql://sendou@localhost:5432/sendou_ink?schema=public
|
|||
// you can get them by making an application on https://discord.com/developers
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
DISCORD_CALLBACK_URL=http://localhost:3001/auth/discord/callback
|
||||
DISCORD_CALLBACK_URL=http://localhost:3000/auth/discord/callback
|
||||
COOKIE_SECRET=
|
||||
FRONTEND_URL=http://localhost:3000
|
||||
|
||||
VITE_BACKEND_URL=http://localhost:3001
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import { useUserContext } from "~/root";
|
||||
import { DiscordIcon } from "../icons/Discord";
|
||||
|
||||
// TODO: redirect to same page on login
|
||||
export function UserItem() {
|
||||
const user = null;
|
||||
const user = useUserContext();
|
||||
|
||||
if (user)
|
||||
return (
|
||||
<img
|
||||
className="layout__avatar"
|
||||
src={`https://cdn.discordapp.com/avatars/discordId/discordAvatar.png?size=80`}
|
||||
src={`https://cdn.discordapp.com/avatars/${user.discordId}/${user.discordAvatar}.png?size=80`}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<form action="/" method="post" data-cy="log-in-form">
|
||||
<form action="/auth/discord" method="post" data-cy="log-in-form">
|
||||
<button
|
||||
type="submit"
|
||||
className="layout__log-in-button"
|
||||
|
|
|
|||
30
app/root.tsx
30
app/root.tsx
|
|
@ -7,8 +7,9 @@ import {
|
|||
Scripts,
|
||||
ScrollRestoration,
|
||||
useCatch,
|
||||
useLoaderData,
|
||||
} from "remix";
|
||||
import type { LinksFunction } from "remix";
|
||||
import type { LinksFunction, LoaderFunction } from "remix";
|
||||
|
||||
import normalizeStylesUrl from "~/styles/normalize.css";
|
||||
import globalStylesUrl from "~/styles/global.css";
|
||||
|
|
@ -23,12 +24,33 @@ export const links: LinksFunction = () => {
|
|||
];
|
||||
};
|
||||
|
||||
export const loader: LoaderFunction = ({ context }) => {
|
||||
const { user } = context;
|
||||
console.log({ user });
|
||||
|
||||
return user ?? null;
|
||||
};
|
||||
|
||||
type LoggedInUser = {
|
||||
id: number;
|
||||
discordId: string;
|
||||
discordAvatar: string;
|
||||
} | null;
|
||||
|
||||
const UserContext = React.createContext<LoggedInUser>(null);
|
||||
|
||||
export const useUserContext = () => React.useContext<LoggedInUser>(UserContext);
|
||||
|
||||
export default function App() {
|
||||
const data = useLoaderData();
|
||||
|
||||
return (
|
||||
<Document>
|
||||
<Layout>
|
||||
<Outlet />
|
||||
</Layout>
|
||||
<UserContext.Provider value={data}>
|
||||
<Layout>
|
||||
<Outlet />
|
||||
</Layout>
|
||||
</UserContext.Provider>
|
||||
</Document>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
62
app/services/user.ts
Normal file
62
app/services/user.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import prisma from "../../prisma/client";
|
||||
import type { Strategy as DiscordStrategy } from "passport-discord";
|
||||
|
||||
export async function upsertUser({
|
||||
loggedInUser,
|
||||
refreshToken,
|
||||
}: {
|
||||
loggedInUser: DiscordStrategy.Profile;
|
||||
refreshToken: string;
|
||||
}) {
|
||||
return prisma.user.upsert({
|
||||
create: {
|
||||
discordId: loggedInUser.id,
|
||||
discordName: loggedInUser.username,
|
||||
discordDiscriminator: loggedInUser.discriminator,
|
||||
discordAvatar: loggedInUser.avatar,
|
||||
discordRefreshToken: refreshToken,
|
||||
...parseConnections(loggedInUser.connections),
|
||||
},
|
||||
update: {
|
||||
discordName: loggedInUser.username,
|
||||
discordDiscriminator: loggedInUser.discriminator,
|
||||
discordAvatar: loggedInUser.avatar,
|
||||
discordRefreshToken: refreshToken,
|
||||
...parseConnections(loggedInUser.connections),
|
||||
},
|
||||
where: {
|
||||
discordId: loggedInUser.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function parseConnections(
|
||||
connections: DiscordStrategy.ConnectionInfo[] | undefined
|
||||
) {
|
||||
if (!connections) return null;
|
||||
|
||||
const result: {
|
||||
twitch?: string;
|
||||
twitter?: string;
|
||||
youtubeId?: string;
|
||||
youtubeName?: string;
|
||||
} = {};
|
||||
|
||||
for (const connection of connections) {
|
||||
if (connection.visibility !== 1 || !connection.verified) continue;
|
||||
|
||||
switch (connection.type) {
|
||||
case "twitch":
|
||||
result.twitch = connection.name;
|
||||
break;
|
||||
case "twitter":
|
||||
result.twitter = connection.name;
|
||||
break;
|
||||
case "youtube":
|
||||
result.youtubeId = connection.id;
|
||||
result.youtubeName = connection.name;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
399
package-lock.json
generated
399
package-lock.json
generated
|
|
@ -12,9 +12,13 @@
|
|||
"@remix-run/react": "^1.0.6",
|
||||
"classnames": "^2.3.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.2",
|
||||
"morgan": "^1.10.0",
|
||||
"passport": "^0.5.0",
|
||||
"passport-discord": "^0.1.4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"remix": "^1.0.6",
|
||||
|
|
@ -23,9 +27,13 @@
|
|||
"devDependencies": {
|
||||
"@remix-run/dev": "^1.0.6",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.4",
|
||||
"@types/faker": "^5.5.9",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/passport": "^1.0.7",
|
||||
"@types/passport-discord": "^0.1.5",
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"concurrently": "^6.4.0",
|
||||
|
|
@ -403,6 +411,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"node_modules/@types/cookie-parser": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz",
|
||||
"integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
||||
|
|
@ -450,6 +467,15 @@
|
|||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-session": {
|
||||
"version": "1.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz",
|
||||
"integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/faker": {
|
||||
"version": "5.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/faker/-/faker-5.5.9.tgz",
|
||||
|
|
@ -542,12 +568,52 @@
|
|||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/oauth": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz",
|
||||
"integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/passport": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz",
|
||||
"integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-discord": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-discord/-/passport-discord-0.1.5.tgz",
|
||||
"integrity": "sha512-hq/EcxU+gKaGdgTAX9LDMEt+/FmDJphq84qRUt5jt553a5RPCwxonb7QwOZUO3XBhzLTXIbJmPQd5/5bTXJnyA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/passport": "*",
|
||||
"@types/passport-oauth2": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-oauth2": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.11.tgz",
|
||||
"integrity": "sha512-KUNwmGhe/3xPbjkzkPwwcPmyFwfyiSgtV1qOrPBLaU4i4q9GSCdAOyCbkFG0gUxAyEmYwqo9OAF/rjPjJ6ImdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/oauth": "*",
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
|
||||
|
|
@ -796,6 +862,14 @@
|
|||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64url": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
|
||||
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
|
|
@ -1318,6 +1392,23 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser/node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.1.0.tgz",
|
||||
|
|
@ -2040,6 +2131,56 @@
|
|||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.17.2",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz",
|
||||
"integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.0.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"node_modules/express-session/node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/express/node_modules/cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
|
|
@ -4318,6 +4459,11 @@
|
|||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/oauth": {
|
||||
"version": "0.9.15",
|
||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||
"integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
|
@ -4481,6 +4627,57 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/passport": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz",
|
||||
"integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==",
|
||||
"dependencies": {
|
||||
"passport-strategy": "1.x.x",
|
||||
"pause": "0.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jaredhanson"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-discord": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/passport-discord/-/passport-discord-0.1.4.tgz",
|
||||
"integrity": "sha512-VJWPYqSOmh7SaCLw/C+k1ZqCzJnn2frrmQRx1YrcPJ3MQ+Oa31XclbbmqFICSvl8xv3Fqd6YWQ4H4p1MpIN9rA==",
|
||||
"dependencies": {
|
||||
"passport-oauth2": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-oauth2": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.1.tgz",
|
||||
"integrity": "sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ==",
|
||||
"dependencies": {
|
||||
"base64url": "3.x.x",
|
||||
"oauth": "0.9.x",
|
||||
"passport-strategy": "1.x.x",
|
||||
"uid2": "0.0.x",
|
||||
"utils-merge": "1.x.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jaredhanson"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-strategy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
||||
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
|
|
@ -4527,6 +4724,11 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pause": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
|
||||
},
|
||||
"node_modules/periscopic": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.0.4.tgz",
|
||||
|
|
@ -4745,6 +4947,14 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
|
|
@ -5954,6 +6164,22 @@
|
|||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/uid2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
|
||||
"integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA=="
|
||||
},
|
||||
"node_modules/unified": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.1.tgz",
|
||||
|
|
@ -6768,6 +6994,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"@types/cookie-parser": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz",
|
||||
"integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/debug": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
||||
|
|
@ -6815,6 +7050,15 @@
|
|||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/express-session": {
|
||||
"version": "1.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz",
|
||||
"integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/faker": {
|
||||
"version": "5.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/faker/-/faker-5.5.9.tgz",
|
||||
|
|
@ -6907,12 +7151,52 @@
|
|||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/oauth": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz",
|
||||
"integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/passport": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz",
|
||||
"integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/passport-discord": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-discord/-/passport-discord-0.1.5.tgz",
|
||||
"integrity": "sha512-hq/EcxU+gKaGdgTAX9LDMEt+/FmDJphq84qRUt5jt553a5RPCwxonb7QwOZUO3XBhzLTXIbJmPQd5/5bTXJnyA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*",
|
||||
"@types/passport": "*",
|
||||
"@types/passport-oauth2": "*"
|
||||
}
|
||||
},
|
||||
"@types/passport-oauth2": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.11.tgz",
|
||||
"integrity": "sha512-KUNwmGhe/3xPbjkzkPwwcPmyFwfyiSgtV1qOrPBLaU4i4q9GSCdAOyCbkFG0gUxAyEmYwqo9OAF/rjPjJ6ImdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*",
|
||||
"@types/oauth": "*",
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
|
||||
|
|
@ -7115,6 +7399,11 @@
|
|||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"base64url": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
|
||||
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
|
||||
},
|
||||
"basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
|
|
@ -7513,6 +7802,22 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||
"requires": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.1.0.tgz",
|
||||
|
|
@ -8023,6 +8328,38 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"express-session": {
|
||||
"version": "1.17.2",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz",
|
||||
"integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==",
|
||||
"requires": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.0.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
|
|
@ -9622,6 +9959,11 @@
|
|||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
|
||||
"dev": true
|
||||
},
|
||||
"oauth": {
|
||||
"version": "0.9.15",
|
||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||
"integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
|
@ -9736,6 +10078,40 @@
|
|||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"passport": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz",
|
||||
"integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==",
|
||||
"requires": {
|
||||
"passport-strategy": "1.x.x",
|
||||
"pause": "0.0.1"
|
||||
}
|
||||
},
|
||||
"passport-discord": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/passport-discord/-/passport-discord-0.1.4.tgz",
|
||||
"integrity": "sha512-VJWPYqSOmh7SaCLw/C+k1ZqCzJnn2frrmQRx1YrcPJ3MQ+Oa31XclbbmqFICSvl8xv3Fqd6YWQ4H4p1MpIN9rA==",
|
||||
"requires": {
|
||||
"passport-oauth2": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"passport-oauth2": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.1.tgz",
|
||||
"integrity": "sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ==",
|
||||
"requires": {
|
||||
"base64url": "3.x.x",
|
||||
"oauth": "0.9.x",
|
||||
"passport-strategy": "1.x.x",
|
||||
"uid2": "0.0.x",
|
||||
"utils-merge": "1.x.x"
|
||||
}
|
||||
},
|
||||
"passport-strategy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
||||
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
|
|
@ -9770,6 +10146,11 @@
|
|||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"dev": true
|
||||
},
|
||||
"pause": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
|
||||
},
|
||||
"periscopic": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.0.4.tgz",
|
||||
|
|
@ -9917,6 +10298,11 @@
|
|||
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
|
||||
"dev": true
|
||||
},
|
||||
"random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
|
|
@ -10825,6 +11211,19 @@
|
|||
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
|
||||
"dev": true
|
||||
},
|
||||
"uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"requires": {
|
||||
"random-bytes": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"uid2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
|
||||
"integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA=="
|
||||
},
|
||||
"unified": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.1.tgz",
|
||||
|
|
|
|||
|
|
@ -25,9 +25,13 @@
|
|||
"@remix-run/react": "^1.0.6",
|
||||
"classnames": "^2.3.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.2",
|
||||
"morgan": "^1.10.0",
|
||||
"passport": "^0.5.0",
|
||||
"passport-discord": "^0.1.4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"remix": "^1.0.6",
|
||||
|
|
@ -36,9 +40,13 @@
|
|||
"devDependencies": {
|
||||
"@remix-run/dev": "^1.0.6",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.4",
|
||||
"@types/faker": "^5.5.9",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/passport": "^1.0.7",
|
||||
"@types/passport-discord": "^0.1.5",
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"concurrently": "^6.4.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import pkg from "@prisma/client";
|
||||
const { PrismaClient } = pkg;
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
|
|
|
|||
74
server/auth.ts
Normal file
74
server/auth.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import type { Express } from "express";
|
||||
import invariant from "tiny-invariant";
|
||||
import session from "express-session";
|
||||
import cookieParser from "cookie-parser";
|
||||
import passport from "passport";
|
||||
import { Strategy as DiscordStrategy } from "passport-discord";
|
||||
import { upsertUser } from "../app/services/user";
|
||||
|
||||
export function setUpAuth(app: Express): void {
|
||||
invariant(
|
||||
process.env.DISCORD_CLIENT_ID,
|
||||
"env var DISCORD_CLIENT_ID undefined"
|
||||
);
|
||||
invariant(
|
||||
process.env.DISCORD_CLIENT_SECRET,
|
||||
"env var DISCORD_CLIENT_SECRET undefined"
|
||||
);
|
||||
invariant(
|
||||
process.env.DISCORD_CALLBACK_URL,
|
||||
"env var DISCORD_CALLBACK_URL undefined"
|
||||
);
|
||||
invariant(process.env.COOKIE_SECRET, "env var COOKIE_SECRET undefined");
|
||||
|
||||
passport.use(
|
||||
new DiscordStrategy(
|
||||
{
|
||||
clientID: process.env.DISCORD_CLIENT_ID,
|
||||
clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
||||
callbackURL: process.env.DISCORD_CALLBACK_URL,
|
||||
scope: ["identify", "connections"],
|
||||
},
|
||||
function (_accessToken, refreshToken, loggedInUser, cb) {
|
||||
upsertUser({ loggedInUser, refreshToken })
|
||||
.then((user) => {
|
||||
return cb(null, {
|
||||
id: user.id,
|
||||
discordId: user.discordId,
|
||||
discordAvatar: user.discordAvatar,
|
||||
});
|
||||
})
|
||||
.catch((err) => cb(err));
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
passport.serializeUser(function (user, done) {
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function (user, done) {
|
||||
// @ts-expect-error it is guaranteed it's of a certain shape without an extra check
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.COOKIE_SECRET,
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
})
|
||||
);
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
app.post("/auth/discord", passport.authenticate("discord"));
|
||||
app.get(
|
||||
"/auth/discord/callback",
|
||||
passport.authenticate("discord", {
|
||||
failureRedirect: "/login",
|
||||
// TODO: fix for prod + redirect to same page
|
||||
successRedirect: "http://localhost:3000",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import express from "express";
|
|||
import compression from "compression";
|
||||
import morgan from "morgan";
|
||||
import { createRequestHandler } from "@remix-run/express";
|
||||
import { setUpAuth } from "./auth";
|
||||
|
||||
const MODE = process.env.NODE_ENV;
|
||||
const BUILD_DIR = path.join(process.cwd(), "server/build");
|
||||
|
|
@ -17,14 +18,32 @@ app.use(express.static("public", { maxAge: "1h" }));
|
|||
app.use(express.static("public/build", { immutable: true, maxAge: "1y" }));
|
||||
|
||||
app.use(morgan("tiny"));
|
||||
|
||||
try {
|
||||
setUpAuth(app);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
function userToContext(req: Express.Request) {
|
||||
return { user: req.user };
|
||||
}
|
||||
|
||||
app.all(
|
||||
"*",
|
||||
MODE === "production"
|
||||
? createRequestHandler({ build: require("./build") })
|
||||
? createRequestHandler({
|
||||
build: require("./build"),
|
||||
getLoadContext: userToContext,
|
||||
})
|
||||
: (req, res, next) => {
|
||||
purgeRequireCache();
|
||||
const build = require("./build");
|
||||
return createRequestHandler({ build, mode: MODE })(req, res, next);
|
||||
return createRequestHandler({
|
||||
build,
|
||||
mode: MODE,
|
||||
getLoadContext: userToContext,
|
||||
})(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user