diff --git a/environment.d.ts b/environment.d.ts index 1eb7543..b66448a 100644 --- a/environment.d.ts +++ b/environment.d.ts @@ -4,6 +4,11 @@ declare global { DISCORD_CLIENT_ID: string; DISCORD_CLIENT_SECRET: string; DISCORD_ALLOWED_GUILDS: string; + + LDAP_HOST: string; + LDAP_BIND_USER: string; + LDAP_BIND_PASSWORD: string; + BASE_DOMAIN: string; } } diff --git a/package-lock.json b/package-lock.json index afa6e12..9687d53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@types/react-dom": "18.0.11", "eslint": "8.39.0", "eslint-config-next": "13.3.1", + "ldapts": "^4.2.5", "next": "13.3.1", "react": "18.2.0", "react-dom": "18.2.0", @@ -362,6 +363,14 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/asn1": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/asn1/-/asn1-0.2.0.tgz", + "integrity": "sha512-5TMxIpYbIA9c1J0hYQjQDX3wr+rTgQEAXaW2BI8ECM8FO53wSW4HFZplTalrKSHuZUc76NtXcePRhwuOHqGD5g==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -403,8 +412,7 @@ "node_modules/@types/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", - "dev": true + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==" }, "node_modules/@typescript-eslint/parser": { "version": "5.59.1", @@ -655,6 +663,14 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -2270,6 +2286,23 @@ "language-subtag-registry": "~0.3.2" } }, + "node_modules/ldapts": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-4.2.5.tgz", + "integrity": "sha512-4R+q+TGFBoXqERwWWvZPF/x1X4y5vdoHhaPWzs8gHXG6mIGJrrGanUWTb7+wZ9G8+razNQ1d8tHv94mFD+3jHQ==", + "dependencies": { + "@types/asn1": ">=0.2.0", + "@types/node": ">=14", + "@types/uuid": ">=9", + "asn1": "~0.2.6", + "debug": "~4.3.4", + "strict-event-emitter-types": "~2.0.0", + "uuid": "~9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2902,6 +2935,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -2991,6 +3029,11 @@ "node": ">=10.0.0" } }, + "node_modules/strict-event-emitter-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz", + "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==" + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", @@ -3575,6 +3618,14 @@ "tslib": "^2.4.0" } }, + "@types/asn1": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/asn1/-/asn1-0.2.0.tgz", + "integrity": "sha512-5TMxIpYbIA9c1J0hYQjQDX3wr+rTgQEAXaW2BI8ECM8FO53wSW4HFZplTalrKSHuZUc76NtXcePRhwuOHqGD5g==", + "requires": { + "@types/node": "*" + } + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -3616,8 +3667,7 @@ "@types/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", - "dev": true + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==" }, "@typescript-eslint/parser": { "version": "5.59.1", @@ -3775,6 +3825,14 @@ "get-intrinsic": "^1.1.3" } }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -4941,6 +4999,20 @@ "language-subtag-registry": "~0.3.2" } }, + "ldapts": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-4.2.5.tgz", + "integrity": "sha512-4R+q+TGFBoXqERwWWvZPF/x1X4y5vdoHhaPWzs8gHXG6mIGJrrGanUWTb7+wZ9G8+razNQ1d8tHv94mFD+3jHQ==", + "requires": { + "@types/asn1": ">=0.2.0", + "@types/node": ">=14", + "@types/uuid": ">=9", + "asn1": "~0.2.6", + "debug": "~4.3.4", + "strict-event-emitter-types": "~2.0.0", + "uuid": "~9.0.0" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5334,6 +5406,11 @@ "is-regex": "^1.1.4" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -5396,6 +5473,11 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" }, + "strict-event-emitter-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz", + "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==" + }, "string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", diff --git a/package.json b/package.json index 5840de6..1234e0b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/react-dom": "18.0.11", "eslint": "8.39.0", "eslint-config-next": "13.3.1", + "ldapts": "^4.2.5", "next": "13.3.1", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/src/app/api/register/route.ts b/src/app/api/register/route.ts new file mode 100644 index 0000000..8961801 --- /dev/null +++ b/src/app/api/register/route.ts @@ -0,0 +1,18 @@ +export async function POST(request: Request) { + const form = await request.formData(); + let username = form.get("username"); + let displayName = form.get("display-name"); + let email = form.get("email"); + let avatar = form.get("avatar"); + + let password = form.get("password"); + let confirmPassword = form.get("confirm-password"); + + if (!username || !displayName || !email || !password || !confirmPassword) + return new Response("Missing required fields", { status: 400 }); + + if (password !== confirmPassword) + return new Response("Passwords do not match", { status: 400 }); + + return new Response(":3", { status: 200 }); +} diff --git a/src/app/globals.css b/src/app/globals.css index f986282..e862c15 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,3 +1,31 @@ +@property --bg { + syntax: ""; + inherits: true; + initial-value: #2d2a2e; +} + +@property --fg { + syntax: ""; + inherits: true; + initial-value: #fcfcfa; +} + +@property --bg-alt { + syntax: ""; + inherits: true; + initial-value: #403e41; +} + +@property --fg-alt { + syntax: ""; + inherits: true; + initial-value: #727072; +} + +:root { + --theme-transition: 0.5s ease; +} + * { box-sizing: border-box; padding: 0; @@ -8,15 +36,27 @@ html, body { max-width: 100vw; overflow-x: hidden; + color: var(--fg); + background-color: var(--bg); + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +html, body, input, label { + transition: background-color var(--theme-transition), + color var(--theme-transition); +} + +input, button { + font: inherit; + color: inherit; +} + +input::placeholder { + color: var(--fg-alt); + transition: color var(--theme-transition); } a { color: inherit; text-decoration: none; } - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 63fc0b2..a13c001 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,3 +1,4 @@ +import ColorChanger from "@/components/ColorChanger"; import "./globals.css"; import { Inter } from "next/font/google"; @@ -16,7 +17,11 @@ export default function RootLayout({ }) { return ( - {children} + + {children} + + + ); } diff --git a/src/app/oauth/discord/oauth.ts b/src/app/oauth/discord/oauth.ts index 5b1c415..841141f 100644 --- a/src/app/oauth/discord/oauth.ts +++ b/src/app/oauth/discord/oauth.ts @@ -8,6 +8,14 @@ export type DiscordAccessTokenResponse = { scope: string; }; +export type DiscordUserResponse = { + id: string; +}; + +export type DiscordGuildResponse = { + id: string; +}; + export function discordRedirectUri() { return `${process.env.BASE_DOMAIN}oauth/discord/redirect`; } @@ -18,8 +26,18 @@ export async function getDiscordID(token: string) { Authorization: `Bearer ${token}` } }); - const res: { id: string } = await req.json(); + const res: DiscordUserResponse = await req.json(); return res.id; } +export async function getDiscordGuilds(token: string) { + const req = await fetch("https://discord.com/api/users/@me/guilds", { + headers: { + Authorization: `Bearer ${token}` + } + }); + const res: DiscordGuildResponse[] = await req.json(); + return res.map((guild) => guild.id); +} + export const makeTicket = (): string => v4(); diff --git a/src/app/oauth/discord/redirect/route.ts b/src/app/oauth/discord/redirect/route.ts index 5dab15e..4aa711a 100644 --- a/src/app/oauth/discord/redirect/route.ts +++ b/src/app/oauth/discord/redirect/route.ts @@ -3,7 +3,8 @@ import { discordRedirectUri, DiscordAccessTokenResponse, makeTicket, - getDiscordID + getDiscordID, + getDiscordGuilds } from "../oauth"; import { cookies } from "next/dist/client/components/headers"; import prisma from "@/prisma"; @@ -40,7 +41,15 @@ export async function GET(request: Request) { }); if (!tokenResponse.ok) throw "baby"; let tokenBody: DiscordAccessTokenResponse = await tokenResponse.json(); + const id = await getDiscordID(tokenBody.access_token); + const guilds = await getDiscordGuilds(tokenBody.access_token); + const allowedGuilds = process.env.DISCORD_ALLOWED_GUILDS?.split(",") ?? []; + + let allowed = false; + for (const guild of allowedGuilds) if (guilds.includes(guild)) allowed = true; + if (!allowed) + return new Response("not permitted to register account", { status: 403 }); const user = await prisma.authTicket.create({ data: { diff --git a/src/app/page.module.css b/src/app/page.module.css index f2da1ff..2c958a0 100644 --- a/src/app/page.module.css +++ b/src/app/page.module.css @@ -1,5 +1,6 @@ .main { display: flex; + flex-direction: column; justify-content: center; align-items: center; diff --git a/src/app/page.tsx b/src/app/page.tsx index ed67766..c76b032 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,6 +3,8 @@ import styles from "./page.module.css"; export default function Home() { return (
+ gluestick logo +

:3
diff --git a/src/app/register/RegisterForm.module.css b/src/app/register/RegisterForm.module.css new file mode 100644 index 0000000..f28ee63 --- /dev/null +++ b/src/app/register/RegisterForm.module.css @@ -0,0 +1,45 @@ +.form { + max-width: 500px; +} + +.form input[type=submit] { + padding: 1rem 1.5rem; + font-size: 140%; + background: var(--bg-alt); + border: 0; + border-radius: 0.15rem; + cursor: pointer; + font-weight: 600; +} + +.buttonContainer { + display: flex; + justify-content: center; + margin: 2rem 0; +} + +.formRow { + margin: 1rem 0; +} + +.formRow label { + display: block; + font-variant: all-small-caps; + font-size: 105%; +} + +.formRow input { + padding: 0.5em 1em; + border: none; + border-radius: 0.15rem; + margin: 0.5rem 0; + width: 250px; + display: block; + background: var(--bg-alt); +} + +.hint { + color: var(--fg-alt); + font-size: 80%; + transition: color var(--theme-transition); +} \ No newline at end of file diff --git a/src/app/register/RegisterForm.tsx b/src/app/register/RegisterForm.tsx new file mode 100644 index 0000000..7591c95 --- /dev/null +++ b/src/app/register/RegisterForm.tsx @@ -0,0 +1,101 @@ +"use client"; + +import React, { InputHTMLAttributes } from "react"; +import { HTMLInputTypeAttribute } from "react"; +import styles from "./RegisterForm.module.css"; + +function Input({ + label, + name, + hint, + type, + placeholder, + ...props +}: { + label: string; + name: string; + hint?: string; + type: HTMLInputTypeAttribute; + placeholder?: string; +} & InputHTMLAttributes) { + const id = React.useId(); + + return ( +

+ + +

{hint}

+
+ ); +} + +export default function RegisterForm() { + return ( +
+
+ + + + + + + + + + + + +
+ +
+
+
+ ); +} diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index a4e24da..c582c7b 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -1,5 +1,6 @@ import { cookies } from "next/dist/client/components/headers"; -import styles from "../page.module.css"; +import styles from "@/app/page.module.css"; +import RegisterForm from "./RegisterForm"; export default function Page() { const cookieStore = cookies(); @@ -10,39 +11,7 @@ export default function Page() { return (
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
+
); } diff --git a/src/components/ColorChanger.module.css b/src/components/ColorChanger.module.css new file mode 100644 index 0000000..0b27b54 --- /dev/null +++ b/src/components/ColorChanger.module.css @@ -0,0 +1,15 @@ +.color-changer { + position: absolute; + right: 0; + bottom: 0; + + width: 40px; + padding: 8px; + + opacity: 30%; + transition: opacity 0.25s ease-in-out; +} + +.color-changer:hover { + opacity: 100%; +} diff --git a/src/components/ColorChanger.tsx b/src/components/ColorChanger.tsx new file mode 100644 index 0000000..3b8fba0 --- /dev/null +++ b/src/components/ColorChanger.tsx @@ -0,0 +1,59 @@ +"use client"; + +import React from "react"; +import styles from "./ColorChanger.module.css"; + +const colors = [ + { + name: "Monokai Pro", + bg: "#2d2a2e", + bgAlt: "#403e41", + fg: "#fcfcfa", + fgAlt: "#727072" + }, + { + name: "Gruvbox Dark", + bg: "#282828", + bgAlt: "#3c3836", + fg: "#ebdbb2", + fgAlt: "#a89984" + }, + { + name: "Amora", + bg: "#1a1a1a", + bgAlt: "#171717", + fg: "#DEDBEB", + fgAlt: "#5c5c5c" + } +]; + +export default function ColorChanger() { + const [current, setCurrent] = React.useState("Monokai Pro"); + + return ( + paint { + const pool = colors.filter((x) => x.name !== current); + const idx = Math.floor(Math.random() * pool.length); + const colorScheme = pool[idx]; + + const fixedColors = { + "--bg": colorScheme.bg, + "--bg-alt": colorScheme.bgAlt, + "--fg": colorScheme.fg, + "--fg-alt": colorScheme.fgAlt + }; + + for (const [k, v] of Object.entries(fixedColors)) { + document.documentElement.style.setProperty(k, v); + } + + setCurrent(colorScheme.name); + }} + className={styles["color-changer"]} + /> + ); +} diff --git a/src/ldap.ts b/src/ldap.ts new file mode 100644 index 0000000..a7a6374 --- /dev/null +++ b/src/ldap.ts @@ -0,0 +1,9 @@ +import { Client } from "ldapts"; + +const client = new Client({ + url: `ldap://${process.env.LDAP_HOST}` +}); + +// await client.bind(process.env.LDAP_USER, process.env.LDAP_BIND_PASSWORD); + +export default client; diff --git a/src/public/paint.svg b/src/public/paint.svg new file mode 100644 index 0000000..cd25814 --- /dev/null +++ b/src/public/paint.svg @@ -0,0 +1,20 @@ + + + + + + Mutant Standard emoji 2020.04 + + + + + Dzuk + http://mutant.tech/ + + + + + \ No newline at end of file