mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-10 04:22:09 +10:00
Merge branch 'develop' into db-store
This commit is contained in:
64
.github/workflows/release.yml
vendored
Normal file
64
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
name: Release Workflow
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
# This can be used to automatically publish nightlies at UTC nighttime
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
|
||||
jobs:
|
||||
web:
|
||||
name: Push website Docker image to registry
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
buildkitd-flags: --debug
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/drop-OSS/drop
|
||||
tags: |
|
||||
type=schedule,pattern=nightly
|
||||
type=semver,pattern=v{{version}}
|
||||
type=semver,pattern=v{{major}}.{{minor}}
|
||||
type=semver,pattern=v{{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=sha
|
||||
# set latest tag for stable releases
|
||||
type=raw,value=latest,enable=${{ github.event.release.prerelease == false }}
|
||||
|
||||
- name: Build and push image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
35
.vscode/settings.json
vendored
35
.vscode/settings.json
vendored
@ -1,18 +1,21 @@
|
||||
{
|
||||
"spellchecker.ignoreWordsList": [
|
||||
"mTLS",
|
||||
"Wireguard"
|
||||
],
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"server": "localhost",
|
||||
"port": 5432,
|
||||
"driver": "PostgreSQL",
|
||||
"name": "drop",
|
||||
"database": "drop",
|
||||
"username": "drop",
|
||||
"password": "drop"
|
||||
}
|
||||
]
|
||||
"spellchecker.ignoreWordsList": ["mTLS", "Wireguard"],
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"server": "localhost",
|
||||
"port": 5432,
|
||||
"driver": "PostgreSQL",
|
||||
"name": "drop",
|
||||
"database": "drop",
|
||||
"username": "drop",
|
||||
"password": "drop"
|
||||
}
|
||||
],
|
||||
// allow autocomplete for ArkType expressions like "string | num"
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
// prioritize ArkType's "type" for autoimports
|
||||
"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^(node:)?os$"]
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# pull pre-configured and updated build environment
|
||||
FROM registry.deepcore.dev/drop-oss/drop-server-build-environment/main:latest AS build-system
|
||||
FROM debian:12.10-slim AS build-system
|
||||
|
||||
# setup workdir
|
||||
RUN mkdir /build
|
||||
|
||||
@ -36,7 +36,9 @@ Your drop server should now be running. To register the admin user, navigate to
|
||||
and fill in the required forms
|
||||
|
||||
### Adding a game
|
||||
|
||||
To add a game to the drop library, do as follows:
|
||||
|
||||
1. Ensure that the current user owns the library folder with `sudo chown -R $(id -u $(whoami)) library`
|
||||
2. `cd library`
|
||||
3. `mkdir <GAME_NAME>` with the name of the game which you would like to register
|
||||
@ -73,7 +75,7 @@ Steps:
|
||||
|
||||
As part of the first-time bootstrap, Drop creates an invitation with the fixed id of 'admin'. So, to create an admin account, go to:
|
||||
|
||||
http://localhost:3000/register?id=admin
|
||||
http://localhost:3000/auth/register?id=admin
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@ -39,7 +39,11 @@
|
||||
</NuxtLink>
|
||||
<div class="h-0.5 rounded-full w-full bg-zinc-800" />
|
||||
<div class="flex flex-col">
|
||||
<MenuItem v-for="(nav, navIdx) in navigation" v-slot="{ active, close }">
|
||||
<MenuItem
|
||||
v-for="(nav, navIdx) in navigation"
|
||||
v-slot="{ active, close }"
|
||||
hydrate-on-visible
|
||||
>
|
||||
<button
|
||||
:href="nav.route"
|
||||
@click="() => navigateTo(nav.route, close)"
|
||||
@ -48,8 +52,8 @@
|
||||
'text-left transition block px-4 py-2 text-sm',
|
||||
]"
|
||||
>
|
||||
{{ nav.label }}</button
|
||||
>
|
||||
{{ nav.label }}
|
||||
</button>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</PanelWidget>
|
||||
@ -81,7 +85,7 @@ const navigation: NavigationItem[] = [
|
||||
},
|
||||
{
|
||||
label: "Sign out",
|
||||
route: "/signout",
|
||||
route: "/auth/signout",
|
||||
prefix: "",
|
||||
},
|
||||
].filter((e) => e !== undefined);
|
||||
|
||||
@ -16,7 +16,7 @@ const showSignIn = statusCode ? statusCode == 403 || statusCode == 401 : false;
|
||||
|
||||
async function signIn() {
|
||||
clearError({
|
||||
redirect: `/signin?redirect=${encodeURIComponent(route.fullPath)}`,
|
||||
redirect: `/auth/signin?redirect=${encodeURIComponent(route.fullPath)}`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div v-if="!noWrapper" class="flex flex-col w-full min-h-screen bg-zinc-900">
|
||||
<UserHeader class="z-50" />
|
||||
<UserHeader class="z-50" hydrate-on-idle />
|
||||
<div class="grow flex">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
<UserFooter class="z-50" hydrate-on-visible />
|
||||
<UserFooter class="z-50" hydrate-on-interaction />
|
||||
</div>
|
||||
<div class="flex w-full min-h-screen bg-zinc-900" v-else>
|
||||
<NuxtPage />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const whitelistedPrefixes = ["/signin", "/register", "/api", "/setup"];
|
||||
const whitelistedPrefixes = ["/auth/signin", "/register", "/api", "/setup"];
|
||||
const requireAdmin = ["/admin"];
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
@ -13,7 +13,10 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
await updateUser();
|
||||
}
|
||||
if (!user.value) {
|
||||
return navigateTo({ path: "/signin", query: { redirect: to.fullPath } });
|
||||
return navigateTo({
|
||||
path: "/auth/signin",
|
||||
query: { redirect: to.fullPath },
|
||||
});
|
||||
}
|
||||
if (
|
||||
requireAdmin.findIndex((e) => to.fullPath.startsWith(e)) != -1 &&
|
||||
|
||||
@ -9,10 +9,12 @@ export default defineNuxtConfig({
|
||||
devtools: { enabled: false },
|
||||
css: ["~/assets/tailwindcss.css", "~/assets/core.scss"],
|
||||
|
||||
experimental: {
|
||||
buildCache: true,
|
||||
},
|
||||
|
||||
vite: {
|
||||
plugins: [
|
||||
tailwindcss()
|
||||
]
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
|
||||
app: {
|
||||
@ -21,7 +23,22 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
"/auth/signin": { prerender: true },
|
||||
"/signout": { prerender: true },
|
||||
|
||||
"/api/**": { cors: true },
|
||||
|
||||
"/api/v1/client/object/*": {
|
||||
security: {
|
||||
rateLimiter: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
nitro: {
|
||||
minify: true,
|
||||
|
||||
experimental: {
|
||||
websocket: true,
|
||||
tasks: true,
|
||||
@ -30,6 +47,8 @@ export default defineNuxtConfig({
|
||||
scheduledTasks: {
|
||||
"0 * * * *": ["cleanup:invitations"],
|
||||
},
|
||||
|
||||
compressPublicAssets: true,
|
||||
},
|
||||
|
||||
extends: ["./drop-base"],
|
||||
@ -39,6 +58,7 @@ export default defineNuxtConfig({
|
||||
"vue3-carousel-nuxt",
|
||||
"nuxt-security",
|
||||
"@nuxt/image",
|
||||
"@nuxt/fonts",
|
||||
],
|
||||
|
||||
carousel: {
|
||||
@ -48,6 +68,8 @@ export default defineNuxtConfig({
|
||||
security: {
|
||||
headers: {
|
||||
contentSecurityPolicy: {
|
||||
"upgrade-insecure-requests": false,
|
||||
|
||||
"img-src": [
|
||||
"'self'",
|
||||
"data:",
|
||||
|
||||
15
package.json
15
package.json
@ -14,10 +14,13 @@
|
||||
"@drop/droplet": "^0.7.0",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@heroicons/vue": "^2.1.5",
|
||||
"@nuxt/fonts": "^0.11.0",
|
||||
"@nuxt/image": "1.9.0",
|
||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||
"@prisma/client": "^6.1.0",
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"argon2": "^0.41.1",
|
||||
"arktype": "^2.1.10",
|
||||
"axios": "^1.7.7",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cookie-es": "^1.2.2",
|
||||
@ -31,13 +34,14 @@
|
||||
"nuxt-security": "2.2.0",
|
||||
"prisma": "^6.1.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sharp": "^0.33.5",
|
||||
"stream": "^0.0.3",
|
||||
"stream-mime-type": "^2.0.0",
|
||||
"turndown": "^7.2.0",
|
||||
"uuid": "^10.0.0",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest",
|
||||
"vue3-carousel-nuxt": "^1.1.3",
|
||||
"vue3-carousel-nuxt": "^1.1.5",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -48,7 +52,7 @@
|
||||
"@types/uuid": "^10.0.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"h3": "^1.13.0",
|
||||
"nitropack": "^2.9.7",
|
||||
"nitropack": "2.11.6",
|
||||
"postcss": "^8.4.47",
|
||||
"sass": "^1.79.4",
|
||||
"tailwindcss": "^4.0.0"
|
||||
@ -59,5 +63,10 @@
|
||||
"@drop/droplet-linux-x64-gnu": "^0.7.0",
|
||||
"@drop/droplet-win32-x64-msvc": "^0.7.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||
"overrides": {
|
||||
"vue3-carousel-nuxt": {
|
||||
"vue3-carousel": "^0.15.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +188,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { XCircleIcon } from "@heroicons/vue/24/solid";
|
||||
import { type } from "arktype";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@ -208,14 +209,20 @@ const username = ref(invitation.data.value?.username);
|
||||
const password = ref("");
|
||||
const confirmPassword = ref(undefined);
|
||||
|
||||
const mailRegex = /^\S+@\S+\.\S+$/;
|
||||
const validEmail = computed(() => mailRegex.test(email.value ?? ""));
|
||||
const validUsername = computed(
|
||||
() =>
|
||||
(username.value?.length ?? 0) >= 5 &&
|
||||
username.value?.toLowerCase() == username.value
|
||||
const emailValidator = type("string.email");
|
||||
const validEmail = computed(
|
||||
() => !(emailValidator(email.value) instanceof type.errors)
|
||||
);
|
||||
|
||||
const usernameValidator = type("string.lower.preformatted >= 5");
|
||||
const validUsername = computed(
|
||||
() => !(usernameValidator(username.value) instanceof type.errors)
|
||||
);
|
||||
|
||||
const passwordValidator = type("string >= 14");
|
||||
const validPassword = computed(
|
||||
() => !(passwordValidator(password.value) instanceof type.errors)
|
||||
);
|
||||
const validPassword = computed(() => (password.value?.length ?? 0) >= 14);
|
||||
const validConfirmPassword = computed(
|
||||
() => password.value == confirmPassword.value
|
||||
);
|
||||
@ -248,7 +255,7 @@ function register_wrapper() {
|
||||
loading.value = true;
|
||||
register()
|
||||
.then(() => {
|
||||
router.push("/signin");
|
||||
router.push("/auth/signin");
|
||||
})
|
||||
.catch((response) => {
|
||||
const message = response.statusMessage || "An unknown error occurred";
|
||||
@ -42,6 +42,6 @@ const user = useUser();
|
||||
user.value = null;
|
||||
|
||||
// Redirect to signin page after signout
|
||||
await $dropFetch("/signout");
|
||||
router.push("/signin");
|
||||
await $dropFetch("/api/v1/auth/signout"); //TODO: add signout api route
|
||||
router.push("/auth/signin");
|
||||
</script>
|
||||
@ -80,7 +80,7 @@
|
||||
</div>
|
||||
|
||||
<!-- recently updated -->
|
||||
<div class="px-4 sm:px-12 py-4">
|
||||
<div class="px-4 sm:px-12 py-4" hydrate-on-visible>
|
||||
<h1 class="text-zinc-100 text-2xl font-bold font-display">
|
||||
Recently updated
|
||||
</h1>
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "LinkedAuthMec" ADD COLUMN "version" INTEGER NOT NULL DEFAULT 1;
|
||||
@ -7,6 +7,7 @@ model LinkedAuthMec {
|
||||
mec AuthMec
|
||||
enabled Boolean @default(true)
|
||||
|
||||
version Int @default(1)
|
||||
credentials Json
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { AuthMec } from "@prisma/client";
|
||||
import { JsonArray } from "@prisma/client/runtime/library";
|
||||
import { type } from "arktype";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import { checkHash } from "~/server/internal/security/simple";
|
||||
import {
|
||||
checkHashArgon2,
|
||||
checkHashBcrypt,
|
||||
} from "~/server/internal/security/simple";
|
||||
import sessionHandler from "~/server/internal/session";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
@ -19,10 +23,10 @@ export default defineEventHandler(async (h3) => {
|
||||
const authMek = await prisma.linkedAuthMec.findFirst({
|
||||
where: {
|
||||
mec: AuthMec.Simple,
|
||||
credentials: {
|
||||
array_starts_with: username,
|
||||
},
|
||||
enabled: true,
|
||||
user: {
|
||||
username,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
@ -39,17 +43,46 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: "Invalid username or password.",
|
||||
});
|
||||
|
||||
const credentials = authMek.credentials as JsonArray;
|
||||
const hash = credentials.at(1);
|
||||
|
||||
if (!hash || !authMek.user.enabled)
|
||||
if (!authMek.user.enabled)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage:
|
||||
"Invalid or disabled account. Please contact the server administrator.",
|
||||
});
|
||||
|
||||
if (!(await checkHash(password, hash.toString())))
|
||||
// LEGACY bcrypt
|
||||
if (authMek.version == 1) {
|
||||
const credentials = authMek.credentials as JsonArray | null;
|
||||
const hash = credentials?.at(1)?.toString();
|
||||
|
||||
if (!hash)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage:
|
||||
"Invalid password state. Please contact the server administrator.",
|
||||
});
|
||||
|
||||
if (!(await checkHashBcrypt(password, hash)))
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Invalid username or password.",
|
||||
});
|
||||
|
||||
// TODO: send user to forgot password screen or something to force them to change their password to new system
|
||||
await sessionHandler.setUserId(h3, authMek.userId, rememberMe);
|
||||
return { result: true, userId: authMek.userId };
|
||||
}
|
||||
|
||||
// V2: argon2
|
||||
const hash = authMek.credentials as string | undefined;
|
||||
if (!hash || typeof hash !== "string")
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage:
|
||||
"Invalid password state. Please contact the server administrator.",
|
||||
});
|
||||
|
||||
if (!(await checkHashArgon2(password, hash)))
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Invalid username or password.",
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
import { AuthMec, Invitation } from "@prisma/client";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import { createHash } from "~/server/internal/security/simple";
|
||||
import {
|
||||
createHashArgon2,
|
||||
} from "~/server/internal/security/simple";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import * as jdenticon from "jdenticon";
|
||||
import objectHandler from "~/server/internal/objects";
|
||||
import { type } from "arktype";
|
||||
import { writeNonLiteralDefaultMessage } from "arktype/internal/parser/shift/operator/default.ts";
|
||||
|
||||
// Only really a simple test, in case people mistype their emails
|
||||
const mailRegex = /^\S+@\S+\.\S+$/;
|
||||
const userValidator = type({
|
||||
username: "string >= 5",
|
||||
email: "string.email",
|
||||
password: "string >= 14",
|
||||
"displayName?": "string | undefined",
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const body = await readBody(h3);
|
||||
@ -27,59 +35,24 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: "Invalid or expired invitation.",
|
||||
});
|
||||
|
||||
const useInvitationOrBodyRequirement = (
|
||||
field: keyof Invitation,
|
||||
check: (v: string) => boolean
|
||||
) => {
|
||||
if (invitation[field]) {
|
||||
return invitation[field].toString();
|
||||
}
|
||||
const user = userValidator(body);
|
||||
if (user instanceof type.errors) {
|
||||
// hover out.summary to see validation errors
|
||||
console.error(user.summary);
|
||||
|
||||
const v: string = body[field]?.toString();
|
||||
const valid = check(v);
|
||||
return valid ? v : undefined;
|
||||
};
|
||||
|
||||
const username = useInvitationOrBodyRequirement(
|
||||
"username",
|
||||
(e) => e.length >= 5
|
||||
);
|
||||
const email = useInvitationOrBodyRequirement("email", (e) =>
|
||||
mailRegex.test(e)
|
||||
);
|
||||
const password = body.password;
|
||||
const displayName = body.displayName || username;
|
||||
|
||||
if (username === undefined)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Username is invalid. Must be more than 5 characters.",
|
||||
});
|
||||
if (username.toLowerCase() != username)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Username must be all lowercase",
|
||||
statusMessage: user.summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (email === undefined)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid email. Must follow the format you@example.com",
|
||||
});
|
||||
// reuse items from invite
|
||||
if (invitation.username !== null) user.username = invitation.username;
|
||||
if (invitation.email !== null) user.email = invitation.email;
|
||||
|
||||
if (!password)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Password empty or missing.",
|
||||
});
|
||||
|
||||
if (password.length < 14)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Password must be 14 or more characters.",
|
||||
});
|
||||
|
||||
const existing = await prisma.user.count({ where: { username: username } });
|
||||
const existing = await prisma.user.count({
|
||||
where: { username: user.username },
|
||||
});
|
||||
if (existing > 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
@ -91,30 +64,33 @@ export default defineEventHandler(async (h3) => {
|
||||
const profilePictureId = uuidv4();
|
||||
await objectHandler.createFromSource(
|
||||
profilePictureId,
|
||||
async () => jdenticon.toPng(username, 256),
|
||||
async () => jdenticon.toPng(user.username, 256),
|
||||
{},
|
||||
[`internal:read`, `${userId}:write`]
|
||||
);
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
displayName,
|
||||
email,
|
||||
profilePicture: profilePictureId,
|
||||
admin: invitation.isAdmin,
|
||||
},
|
||||
});
|
||||
const [linkMec] = await prisma.$transaction([
|
||||
prisma.linkedAuthMec.create({
|
||||
data: {
|
||||
mec: AuthMec.Simple,
|
||||
credentials: await createHashArgon2(user.password),
|
||||
version: 2,
|
||||
user: {
|
||||
create: {
|
||||
id: userId,
|
||||
username: user.username,
|
||||
displayName: user.displayName ?? user.username,
|
||||
email: user.email,
|
||||
profilePicture: profilePictureId,
|
||||
admin: invitation.isAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
user: true,
|
||||
},
|
||||
}),
|
||||
prisma.invitation.delete({ where: { id: invitationId } }),
|
||||
]);
|
||||
|
||||
const hash = await createHash(password);
|
||||
await prisma.linkedAuthMec.create({
|
||||
data: {
|
||||
mec: AuthMec.Simple,
|
||||
credentials: [username, hash],
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.invitation.delete({ where: { id: invitationId } });
|
||||
|
||||
return user;
|
||||
return linkMec.user;
|
||||
});
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import bcrypt from "bcryptjs";
|
||||
import * as argon2 from "argon2";
|
||||
import { type } from "arktype";
|
||||
|
||||
const rounds = 10;
|
||||
|
||||
export async function createHash(password: string) {
|
||||
return bcrypt.hashSync(password, rounds);
|
||||
export async function checkHashBcrypt(password: string, hash: string) {
|
||||
return await bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
export async function checkHash(password: string, hash: string) {
|
||||
return bcrypt.compareSync(password, hash);
|
||||
export async function createHashArgon2(password: string) {
|
||||
return await argon2.hash(password);
|
||||
}
|
||||
|
||||
export async function checkHashArgon2(password: string, hash: string) {
|
||||
return await argon2.verify(hash, password);
|
||||
}
|
||||
@ -1,16 +1,13 @@
|
||||
import { Platform } from "@prisma/client";
|
||||
|
||||
export function parsePlatform(platform: string) {
|
||||
switch (platform) {
|
||||
switch (platform.toLowerCase()) {
|
||||
case "linux":
|
||||
case "Linux":
|
||||
return Platform.Linux;
|
||||
case "windows":
|
||||
case "Windows":
|
||||
return Platform.Windows;
|
||||
case "macOS":
|
||||
case "MacOS":
|
||||
case "mac":
|
||||
case "macos":
|
||||
return Platform.macOS;
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ export default defineNitroPlugin((nitro) => {
|
||||
if (userId) break;
|
||||
return sendRedirect(
|
||||
event,
|
||||
`/signin?redirect=${encodeURIComponent(event.path)}`
|
||||
`/auth/signin?redirect=${encodeURIComponent(event.path)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,5 +3,5 @@ import sessionHandler from "../internal/session";
|
||||
export default defineEventHandler(async (h3) => {
|
||||
await sessionHandler.clearSession(h3);
|
||||
|
||||
return sendRedirect(h3, "/signin");
|
||||
return sendRedirect(h3, "/auth/signin");
|
||||
});
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
"extends": "../.nuxt/tsconfig.server.json",
|
||||
"compilerOptions": {
|
||||
"exactOptionalPropertyTypes": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
"extends": "./.nuxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"exactOptionalPropertyTypes": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user