Compare commits

...

10 Commits

Author SHA1 Message Date
4fe1e34a3e
fix(nuxt): fix readability
fix global text readability in night mode by changing the color to white
2024-12-09 16:00:43 -05:00
0898a622eb
fix(typing): augment node types (kinda)
do some finagling to shut typescript up about "staging" being an invalid value for `process.env.NODE_ENV`
2024-12-09 15:55:05 -05:00
f447c0f88f
refactor(components): remove another bulky console.log 2024-12-09 15:46:50 -05:00
4b6771c0dd
refactor(components): update navbar
- unwrap add story button from div and nuxt-link
- use `onclick` handler to navigate to the "new story" page
- better align the new story button with the rest of the navbar on tablet+ viewports
2024-12-09 15:43:44 -05:00
718fcc6ff8
fix(components): update error boundary
ensure that styling is consistent by surrounding with `a-extract-style`
2024-12-09 15:38:16 -05:00
480655a0ee
refactor(server/middleware): update current user middleware
- check for titlecased version of auth header
- check for token's `sub` field as well as `id`
- ensure we don't select sensitive info when querying the user
- don't throw if there's no user logged in for that request
2024-12-09 15:34:30 -05:00
53409d12da
refactor(api): update session retrieval route
ensure we also check for the titlecased version of `authorization` header
2024-12-08 23:27:47 -05:00
26dfbda2c7
refactor(pages): remove bulky console.log from story page 2024-12-08 23:24:19 -05:00
e6ba48dc2f
refactor(client-side): update story check middleware
if fetching a story returns an error, check that the error code is 403 (forbidden) as well before redirecting
2024-12-08 23:22:40 -05:00
80dbd05468
refactor(pages): login
add loading indicator to button after submit
2024-12-08 22:54:40 -05:00
13 changed files with 116 additions and 75 deletions

View File

@ -46,4 +46,8 @@
body { body {
margin: 0 !important; margin: 0 !important;
} }
body[data-theme="dark"] {
color: #fff;
}
</style> </style>

View File

@ -18,6 +18,7 @@ ${p.error.stack}
`; `;
</script> </script>
<template> <template>
<a-extract-style>
<a-typography-title> We ran into an issue.... :( <br /></a-typography-title> <a-typography-title> We ran into an issue.... :( <br /></a-typography-title>
<a-typography-paragraph <a-typography-paragraph
style="font-size: 125%" style="font-size: 125%"
@ -40,4 +41,5 @@ ${p.error.stack}
<a-typography-text v-html="error.stack"></a-typography-text> <a-typography-text v-html="error.stack"></a-typography-text>
</a-typography-text> </a-typography-text>
</a-typography-paragraph> </a-typography-paragraph>
</a-extract-style>
</template> </template>

View File

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Grid, ItemType } from "ant-design-vue"; import { Grid } from "ant-design-vue";
const bp = Grid.useBreakpoint(); const bp = Grid.useBreakpoint();
const { data, status } = useAuth(); const { data, status } = useAuth();
const itemMap = ref({ const itemMap = ref({
@ -62,16 +63,12 @@
<a-menu-item key="admin" v-if="data?.user?.profile.isAdmin || false"> Admin</a-menu-item> <a-menu-item key="admin" v-if="data?.user?.profile.isAdmin || false"> Admin</a-menu-item>
<a-menu-item key="logout" v-if="!!data?.user"> Logout</a-menu-item> <a-menu-item key="logout" v-if="!!data?.user"> Logout</a-menu-item>
</a-menu> </a-menu>
<div v-if="data?.user"> <a-button class="new-btn" type="primary" tooltip="Post a New Story" @click="() => navigateTo(`/new-story`)" v-if="data?.user">
<nuxt-link to="/new-story">
<a-button type="primary" tooltip="Post a New Story">
<!-- <template #icon> <!-- <template #icon>
</template> --> </template> -->
<icon istyle="regular" name="file-plus" /> <icon istyle="regular" name="file-plus" />
<span style="margin-left: 0.5em"> Post a New Story </span> <span style="margin-left: 0.5em"> Post a New Story </span>
</a-button> </a-button>
</nuxt-link>
</div>
<div class="acbut" v-if="!data?.user"> <div class="acbut" v-if="!data?.user">
<a-button size="large" @click="() => navigateTo('/auth/login')"> Login</a-button> <a-button size="large" @click="() => navigateTo('/auth/login')"> Login</a-button>
<a-button size="large" type="primary" @click="() => navigateTo('/auth/register')"> Register</a-button> <a-button size="large" type="primary" @click="() => navigateTo('/auth/register')"> Register</a-button>
@ -101,4 +98,11 @@
flex-grow: 1.2; flex-grow: 1.2;
justify-content: stretch; justify-content: stretch;
} }
@media (min-width: 768px) {
.new-btn {
align-self: self-end;
margin-top: 1em;
}
}
</style> </style>

View File

@ -4,7 +4,6 @@
import icon from "~/components/icon.vue"; import icon from "~/components/icon.vue";
const story = inject<SingleChapterResult | null>("story"); const story = inject<SingleChapterResult | null>("story");
console.log("storyyy--info", story);
const dark = inject<Ref<boolean>>("dark"); const dark = inject<Ref<boolean>>("dark");
</script> </script>
<template> <template>

1
index.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="typings/node-ext.d.ts">

View File

@ -14,7 +14,7 @@ export const storyMiddleware = defineNuxtRouteMiddleware(async (to, from) => {
console.log("to n from", to, from, data); console.log("to n from", to, from, data);
const { data: story, error } = await useApiFetch<SingleChapterResult>(to.path); const { data: story, error } = await useApiFetch<SingleChapterResult>(to.path);
if (error.value) { if (error.value) {
if (error.value.message.toLocaleLowerCase() == "unauthenticated") { if (error.value.message.toLocaleLowerCase() == "unauthenticated" || error.value.statusCode == "403") {
return navigateTo("/auth/login"); return navigateTo("/auth/login");
} }
return showError(error.value); return showError(error.value);

View File

@ -1,9 +1,10 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
import { IUser } from "@models/user"; import { IUser } from "@models/user";
import { render } from "vue";
import { rc } from "./lib/server/constants"; import { rc } from "./lib/server/constants";
import { defineNuxtConfig } from "nuxt/config"; import { defineNuxtConfig } from "nuxt/config";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import { StorageMounts } from "nitropack";
import { uri } from "./lib/dbconfig";
const cac = { const cac = {
isr: true, isr: true,
@ -141,7 +142,11 @@ export default defineNuxtConfig({
runtimeConfig: rc, runtimeConfig: rc,
typescript: { typescript: {
tsConfig: { tsConfig: {
exclude: ["./.nuxt/types/auth.d.ts"], exclude: ["./.nuxt/types/auth.d.ts", "../**/node_modules/next/**/*.d.ts"],
compilerOptions: {
typeRoots: ["typings/**/*.d.ts"],
types: ["typings/node-ext.d.ts"],
},
}, },
}, },
imports: { imports: {

View File

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive } from "vue"; import { reactive } from "vue";
import { notification } from "ant-design-vue"; import { notification } from "ant-design-vue";
interface FormState { interface FormState {
username: string; username: string;
password: string; password: string;
@ -15,23 +16,26 @@
useHead({ useHead({
title: "Log In", title: "Log In",
}); });
const loadRef = ref<boolean>(false);
const formState = reactive<FormState>({ const formState = reactive<FormState>({
username: "", username: "",
password: "", password: "",
}); });
const darkRef = inject<Ref<boolean>>("dark"); const darkRef = inject<Ref<boolean>>("dark")!;
const onFinish = async (values: any) => { const onFinish = async (values: any) => {
const { signIn } = useAuth(); const { signIn } = useAuth();
try { try {
loadRef.value = true;
await signIn(values, { redirect: true, callbackUrl: "/" }); await signIn(values, { redirect: true, callbackUrl: "/" });
const { data } = useAuth(); const { data } = useAuth();
darkRef.value = data.value.user.profile.nightMode; darkRef!.value = data.value?.user?.profile.nightMode || false;
if (darkRef.value) document.body.dataset.theme = "dark"; if (darkRef!.value) document.body.dataset.theme = "dark";
await navigateTo("/"); await navigateTo("/");
} catch (e: any) { } catch (e: any) {
if (e.data) { if (e.data) {
loadRef.value = false;
notification["error"]({ notification["error"]({
message: h("div", { innerHTML: e.data.message }), message: h("div", { innerHTML: e.data.message }),
}); });
@ -50,7 +54,7 @@
<a-form-item> <a-form-item>
<a-row :justify="'center'" :align="'middle'"> <a-row :justify="'center'" :align="'middle'">
<a-col> <a-col>
<a-button data-testid="login.submit" type="primary" html-type="submit">Log in</a-button> <a-button :loading="loadRef" data-testid="login.submit" type="primary" html-type="submit">Log in</a-button>
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>

View File

@ -16,7 +16,6 @@
const rtr = useRoute(); const rtr = useRoute();
const { data: story, error } = await useApiFetch<SingleChapterResult>(`/story/${rtr.params.id}/${rtr.params.cidx}`); const { data: story, error } = await useApiFetch<SingleChapterResult>(`/story/${rtr.params.id}/${rtr.params.cidx}`);
provide<SingleChapterResult | null>("story", story.value); provide<SingleChapterResult | null>("story", story.value);
console.log("storyyy", story.value?.currentChapter);
console.log(rtr); console.log(rtr);
let dark = inject<boolean>("dark"); let dark = inject<boolean>("dark");
const and = computed(() => { const and = computed(() => {

View File

@ -1,5 +1,5 @@
export default eventHandler((event) => { export default eventHandler((event) => {
let ahead = (getHeaders(event).authorization || "")?.replace("Bearer ", ""); let ahead = (getHeaders(event).authorization || getHeaders(event).Authorization || "")?.replace("Bearer ", "");
if (event.context.currentUser && ahead) { if (event.context.currentUser && ahead) {
return { return {
token: ahead, token: ahead,

View File

@ -1,26 +1,26 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { log } from "@server/logger"; import { IUser, User } from "@models/user";
import { messages } from "@server/constants";
import { User } from "@models/user";
import { AccessToken } from "@models/oauth";
import { IJwt } from "@server/types/authstuff";
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
let ahead = (getHeaders(event).authorization || "")?.replace("Bearer ", ""); let ahead = (getHeaders(event).authorization || getHeaders(event).Authorization || getCookie(event, "rockfic_cookie"))?.replace("Bearer ", "");
if (ahead) { if (ahead) {
let toktok: jwt.JwtPayload; let toktok: any;
let user: IUser | null = null;
try { try {
toktok = jwt.verify(ahead, useRuntimeConfig().jwt) as IJwt; toktok = jwt.verify(ahead, useRuntimeConfig().jwt);
let user = await User.findById(toktok.id as number).exec(); console.log(toktok);
if (user && toktok) event.context.currentUser = user; if (toktok?.sub) {
} catch (e) { user = await User.findById(toktok.sub as number)
const t = await AccessToken.findOne({ token: ahead }); .select("-password -auth -ipLog")
if (!t) .exec();
throw createError({ } else if (toktok.id) {
statusCode: 401, user = await User.findById(toktok.id as number)
message: messages[401], .select("-password -auth -ipLog")
}); .exec();
let user = await User.findById(t.userID); }
} catch (E) {
console.error(E);
} finally {
if (user) event.context.currentUser = user; if (user) event.context.currentUser = user;
} }
} }

View File

@ -8,6 +8,22 @@
"noImplicitThis": false, "noImplicitThis": false,
"verbatimModuleSyntax": false, "verbatimModuleSyntax": false,
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": false,
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true,
"types": [
"./typings/fuck-you.ts",
"./typings/*.d.ts"
],
"typeRoots": [
"typings/**/*.d.ts",
"typings"
]
}, },
"exclude": [
"**/node_modules/next/**/*.d.ts"
],
"include": [
"typings/**/*.d.ts",
"typings/node-ext.d.ts",
"nuxt.config.ts"
]
} }

7
typings/node-ext.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: ("development" | "staging" | "production" | "test") & (string & {});
}
}
}