session state simplification
This commit is contained in:
@@ -7,14 +7,14 @@ const DeletePostButton = lazy(() => import("./DeletePostButton"));
|
||||
|
||||
export interface CardProps {
|
||||
post: PostCardData;
|
||||
privilegeLevel: "anonymous" | "admin" | "user";
|
||||
isAdmin: boolean;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export default function Card(props: CardProps) {
|
||||
return (
|
||||
<div class="bg-base border-text relative z-0 mx-auto h-96 w-full overflow-hidden rounded-lg border shadow-lg lg:w-5/6 xl:w-3/4">
|
||||
<Show when={props.privilegeLevel === "admin"}>
|
||||
<Show when={props.isAdmin}>
|
||||
<div class="border-opacity-20 bg-opacity-40 border-text bg-text absolute top-0 w-full border-b px-2 py-4 backdrop-blur-md md:px-6">
|
||||
<div class="flex justify-between">
|
||||
<Show when={!props.post.published}>
|
||||
@@ -60,7 +60,7 @@ export default function Card(props: CardProps) {
|
||||
</div>
|
||||
<CardLinks
|
||||
postTitle={props.post.title}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAdmin={props.isAdmin}
|
||||
postID={props.post.id}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Spinner } from "~/components/Spinner";
|
||||
export interface CardLinksProps {
|
||||
postTitle: string;
|
||||
postID: number;
|
||||
privilegeLevel: string;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
export default function CardLinks(props: CardLinksProps) {
|
||||
@@ -25,7 +25,7 @@ export default function CardLinks(props: CardLinksProps) {
|
||||
<Spinner size={24} />
|
||||
</Show>
|
||||
</A>
|
||||
<Show when={props.privilegeLevel === "admin"}>
|
||||
<Show when={props.isAdmin}>
|
||||
<A
|
||||
href={`/blog/edit/${props.postID}`}
|
||||
onClick={() => setEditLoading(true)}
|
||||
|
||||
@@ -122,13 +122,10 @@ export default function CommentBlock(props: CommentBlockProps) {
|
||||
);
|
||||
|
||||
const canDelete = () =>
|
||||
props.currentUserID === props.comment.commenter_id ||
|
||||
props.privilegeLevel === "admin";
|
||||
props.currentUserID === props.comment.commenter_id || props.isAdmin;
|
||||
|
||||
const canEdit = () => props.currentUserID === props.comment.commenter_id;
|
||||
|
||||
const isAnonymous = () => props.privilegeLevel === "anonymous";
|
||||
|
||||
const replyIconColor = () => "var(--color-peach)";
|
||||
|
||||
return (
|
||||
@@ -163,12 +160,12 @@ export default function CommentBlock(props: CommentBlockProps) {
|
||||
hasUpvoted()
|
||||
? "fill-green"
|
||||
: `fill-text hover:fill-green ${
|
||||
isAnonymous() ? "tooltip z-50" : ""
|
||||
!props.isAuthenticated ? "tooltip z-50" : ""
|
||||
}`
|
||||
}`}
|
||||
>
|
||||
<ThumbsUpEmoji />
|
||||
<Show when={isAnonymous()}>
|
||||
<Show when={!props.isAuthenticated}>
|
||||
<div class="tooltip-text -ml-16 w-32 text-white">
|
||||
You must be logged in
|
||||
</div>
|
||||
@@ -190,14 +187,14 @@ export default function CommentBlock(props: CommentBlockProps) {
|
||||
hasDownvoted()
|
||||
? "fill-red"
|
||||
: `fill-text hover:fill-red ${
|
||||
isAnonymous() ? "tooltip z-50" : ""
|
||||
!props.isAuthenticated ? "tooltip z-50" : ""
|
||||
}`
|
||||
}`}
|
||||
>
|
||||
<div class="rotate-180">
|
||||
<ThumbsUpEmoji />
|
||||
</div>
|
||||
<Show when={isAnonymous()}>
|
||||
<Show when={!props.isAuthenticated}>
|
||||
<div class="tooltip-text -ml-16 w-32">
|
||||
You must be logged in
|
||||
</div>
|
||||
@@ -309,7 +306,7 @@ export default function CommentBlock(props: CommentBlockProps) {
|
||||
currentUserID={props.currentUserID}
|
||||
reactions={reactions()}
|
||||
showingReactionOptions={showingReactionOptions()}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAuthenticated={props.isAuthenticated}
|
||||
commentReaction={props.commentReaction}
|
||||
/>
|
||||
</div>
|
||||
@@ -325,7 +322,7 @@ export default function CommentBlock(props: CommentBlockProps) {
|
||||
>
|
||||
<CommentInputBlock
|
||||
isReply={true}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAuthenticated={props.isAuthenticated}
|
||||
parent_id={props.comment.id}
|
||||
post_id={props.projectID}
|
||||
currentUserID={props.currentUserID}
|
||||
@@ -348,7 +345,8 @@ export default function CommentBlock(props: CommentBlockProps) {
|
||||
child_comments={props.allComments?.filter(
|
||||
(comment) => comment.parent_comment_id === childComment.id
|
||||
)}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAuthenticated={props.isAuthenticated}
|
||||
isAdmin={props.isAdmin}
|
||||
currentUserID={props.currentUserID}
|
||||
reactionMap={props.reactionMap}
|
||||
level={props.level + 1}
|
||||
|
||||
@@ -84,13 +84,11 @@ export default function CommentDeletionPrompt(
|
||||
onChange={handleNormalDeleteCheckbox}
|
||||
/>
|
||||
<div class="my-auto px-2 text-sm font-normal">
|
||||
{props.privilegeLevel === "admin"
|
||||
? "Confirm User Delete?"
|
||||
: "Confirm Delete?"}
|
||||
{props.isAdmin ? "Confirm User Delete?" : "Confirm Delete?"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={props.privilegeLevel === "admin"}>
|
||||
<Show when={props.isAdmin}>
|
||||
<div class="flex w-full justify-center">
|
||||
<div class="flex pt-4">
|
||||
<input
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function CommentInputBlock(props: CommentInputBlockProps) {
|
||||
}
|
||||
};
|
||||
|
||||
if (props.privilegeLevel === "user" || props.privilegeLevel === "admin") {
|
||||
if (props.isAuthenticated) {
|
||||
return (
|
||||
<div class="flex w-full justify-center select-none">
|
||||
<div class="h-fit w-3/4">
|
||||
|
||||
@@ -6,7 +6,6 @@ import type {
|
||||
UserPublicData,
|
||||
ReactionType,
|
||||
ModificationType,
|
||||
PrivilegeLevel,
|
||||
SortingMode
|
||||
} from "~/types/comment";
|
||||
import CommentInputBlock from "./CommentInputBlock";
|
||||
@@ -20,28 +19,6 @@ const COMMENT_SORTING_OPTIONS: { val: SortingMode }[] = [
|
||||
{ val: "hot" }
|
||||
];
|
||||
|
||||
interface CommentSectionProps {
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
allComments: Comment[];
|
||||
topLevelComments: Comment[];
|
||||
postID: number;
|
||||
reactionMap: Map<number, CommentReaction[]>;
|
||||
currentUserID: string;
|
||||
userCommentMap: Map<UserPublicData, number[]> | undefined;
|
||||
newComment: (commentBody: string, parentCommentID?: number) => Promise<void>;
|
||||
commentSubmitLoading: boolean;
|
||||
toggleModification: (
|
||||
commentID: number,
|
||||
commenterID: string,
|
||||
commentBody: string,
|
||||
modificationType: ModificationType,
|
||||
commenterImage?: string,
|
||||
commenterEmail?: string,
|
||||
commenterDisplayName?: string
|
||||
) => void;
|
||||
commentReaction: (reactionType: ReactionType, commentID: number) => void;
|
||||
}
|
||||
|
||||
export default function CommentSection(props: CommentSectionProps) {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
@@ -66,7 +43,7 @@ export default function CommentSection(props: CommentSectionProps) {
|
||||
<div class="mb-1">
|
||||
<CommentInputBlock
|
||||
isReply={false}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAuthenticated={props.isAuthenticated}
|
||||
post_id={props.postID}
|
||||
socket={undefined}
|
||||
currentUserID={props.currentUserID}
|
||||
@@ -90,7 +67,8 @@ export default function CommentSection(props: CommentSectionProps) {
|
||||
<div id="comments">
|
||||
<CommentSorting
|
||||
topLevelComments={props.topLevelComments}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAuthenticated={props.isAuthenticated}
|
||||
isAdmin={props.isAdmin}
|
||||
postID={props.postID}
|
||||
allComments={props.allComments}
|
||||
reactionMap={props.reactionMap}
|
||||
|
||||
@@ -855,7 +855,8 @@ export default function CommentSectionWrapper(
|
||||
</Show>
|
||||
|
||||
<CommentSection
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAuthenticated={props.isAuthenticated}
|
||||
isAdmin={props.isAdmin}
|
||||
allComments={allComments()}
|
||||
topLevelComments={topLevelComments()}
|
||||
postID={props.id}
|
||||
@@ -875,7 +876,7 @@ export default function CommentSectionWrapper(
|
||||
commenterImage={commenterImageForModification()}
|
||||
commenterEmail={commenterEmailForModification()}
|
||||
commenterDisplayName={commenterDisplayNameForModification()}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAdmin={props.isAdmin}
|
||||
commentDeletionLoading={commentDeletionLoading()}
|
||||
deleteComment={deleteComment}
|
||||
onClose={() => {
|
||||
|
||||
@@ -51,7 +51,8 @@ export default function CommentSorting(props: CommentSortingProps) {
|
||||
child_comments={props.allComments?.filter(
|
||||
(comment) => comment.parent_comment_id === topLevelComment.id
|
||||
)}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
isAuthenticated={props.isAuthenticated}
|
||||
isAdmin={props.isAdmin}
|
||||
currentUserID={props.currentUserID}
|
||||
reactionMap={props.reactionMap}
|
||||
level={0}
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface Tag {
|
||||
export interface PostSortingProps {
|
||||
posts: PostCardData[];
|
||||
tags: Tag[];
|
||||
privilegeLevel: "anonymous" | "admin" | "user";
|
||||
isAdmin: boolean;
|
||||
filters?: string;
|
||||
sort?: string;
|
||||
include?: string;
|
||||
@@ -36,7 +36,7 @@ export default function PostSorting(props: PostSortingProps) {
|
||||
const filteredPosts = createMemo(() => {
|
||||
let filtered = props.posts;
|
||||
|
||||
if (props.privilegeLevel === "admin" && props.status) {
|
||||
if (props.isAdmin && props.status) {
|
||||
if (props.status === "published") {
|
||||
filtered = filtered.filter((post) => post.published === 1);
|
||||
} else if (props.status === "unpublished") {
|
||||
@@ -141,11 +141,7 @@ export default function PostSorting(props: PostSortingProps) {
|
||||
<For each={sortedPosts()}>
|
||||
{(post, index) => (
|
||||
<div class="my-4">
|
||||
<Card
|
||||
post={post}
|
||||
privilegeLevel={props.privilegeLevel}
|
||||
index={index()}
|
||||
/>
|
||||
<Card post={post} isAdmin={props.isAdmin} index={index()} />
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface PostLike {
|
||||
|
||||
export interface SessionDependantLikeProps {
|
||||
currentUserID: string | undefined | null;
|
||||
privilegeLevel: "admin" | "user" | "anonymous";
|
||||
isAuthenticated: boolean;
|
||||
likes: PostLike[];
|
||||
projectID: number;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ export default function SessionDependantLike(props: SessionDependantLikeProps) {
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={props.privilegeLevel !== "anonymous"}
|
||||
when={props.isAuthenticated}
|
||||
fallback={
|
||||
<button class="tooltip flex flex-col">
|
||||
<div class="mx-auto">
|
||||
|
||||
@@ -65,7 +65,7 @@ export const AuthProvider: ParentComponent = (props) => {
|
||||
const email = () => serverAuth()?.email ?? null;
|
||||
const displayName = () => serverAuth()?.displayName ?? null;
|
||||
const userId = () => serverAuth()?.userId ?? null;
|
||||
const isAdmin = () => serverAuth()?.privilegeLevel === "admin";
|
||||
const isAdmin = () => serverAuth()?.isAdmin ?? false;
|
||||
const isEmailVerified = () => serverAuth()?.emailVerified ?? false;
|
||||
|
||||
// Server handles all token refresh logic
|
||||
|
||||
@@ -9,6 +9,7 @@ export const model: { [key: string]: string } = {
|
||||
display_name TEXT,
|
||||
provider TEXT,
|
||||
image TEXT,
|
||||
is_admin INTEGER DEFAULT 0,
|
||||
registered_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
failed_attempts INTEGER DEFAULT 0,
|
||||
locked_until TEXT
|
||||
|
||||
4
src/env/server.ts
vendored
4
src/env/server.ts
vendored
@@ -2,8 +2,6 @@ import { z } from "zod";
|
||||
|
||||
const serverEnvSchema = z.object({
|
||||
NODE_ENV: z.enum(["development", "test", "production"]),
|
||||
ADMIN_EMAIL: z.string().min(1),
|
||||
ADMIN_ID: z.string().min(1),
|
||||
JWT_SECRET_KEY: z.string().min(1),
|
||||
AWS_REGION: z.string().min(1),
|
||||
AWS_S3_BUCKET_NAME: z.string().min(1),
|
||||
@@ -109,8 +107,6 @@ export const isMissingEnvVar = (varName: string): boolean => {
|
||||
export const getMissingEnvVars = (): string[] => {
|
||||
const requiredServerVars = [
|
||||
"NODE_ENV",
|
||||
"ADMIN_EMAIL",
|
||||
"ADMIN_ID",
|
||||
"JWT_SECRET_KEY",
|
||||
"AWS_REGION",
|
||||
"AWS_S3_BUCKET_NAME",
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface UserState {
|
||||
email: string | null;
|
||||
displayName: string | null;
|
||||
emailVerified: boolean;
|
||||
privilegeLevel: "admin" | "user" | "anonymous";
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,7 @@ export const getUserState = query(async (): Promise<UserState> => {
|
||||
email: null,
|
||||
displayName: null,
|
||||
emailVerified: false,
|
||||
privilegeLevel: "anonymous"
|
||||
isAdmin: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const getUserState = query(async (): Promise<UserState> => {
|
||||
email: null,
|
||||
displayName: null,
|
||||
emailVerified: false,
|
||||
privilegeLevel: "anonymous"
|
||||
isAdmin: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export const getUserState = query(async (): Promise<UserState> => {
|
||||
email: null,
|
||||
displayName: null,
|
||||
emailVerified: false,
|
||||
privilegeLevel: "anonymous"
|
||||
isAdmin: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const getUserState = query(async (): Promise<UserState> => {
|
||||
email: user.email ?? null,
|
||||
displayName: user.display_name ?? null,
|
||||
emailVerified: user.email_verified === 1,
|
||||
privilegeLevel: auth.isAdmin ? "admin" : "user"
|
||||
isAdmin: auth.isAdmin
|
||||
};
|
||||
}, "user-auth-state");
|
||||
|
||||
|
||||
@@ -50,15 +50,14 @@ export function isValidCommentBody(body: string): boolean {
|
||||
export function canModifyComment(
|
||||
userID: string,
|
||||
commenterID: string,
|
||||
privilegeLevel: "admin" | "user" | "anonymous"
|
||||
isAuthenticated: boolean,
|
||||
isAdmin: boolean
|
||||
): boolean {
|
||||
if (privilegeLevel === "admin") return true;
|
||||
if (privilegeLevel === "anonymous") return false;
|
||||
if (isAdmin) return true;
|
||||
if (!isAuthenticated) return false;
|
||||
return userID === commenterID;
|
||||
}
|
||||
|
||||
export function canDatabaseDelete(
|
||||
privilegeLevel: "admin" | "user" | "anonymous"
|
||||
): boolean {
|
||||
return privilegeLevel === "admin";
|
||||
export function canDatabaseDelete(isAdmin: boolean): boolean {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const checkAdmin = query(async (): Promise<boolean> => {
|
||||
const { getUserState } = await import("~/lib/auth-query");
|
||||
const userState = await getUserState();
|
||||
|
||||
if (userState.privilegeLevel !== "admin") {
|
||||
if (!userState.isAdmin) {
|
||||
console.log("redirect");
|
||||
throw redirect("/");
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ const getPostByTitle = query(
|
||||
const { getFeatureFlags } = await import("~/server/feature-flags");
|
||||
const event = getRequestEvent()!;
|
||||
const userState = await getUserState();
|
||||
const privilegeLevel = userState.privilegeLevel;
|
||||
const isAuthenticated = userState.isAuthenticated;
|
||||
const isAdmin = userState.isAdmin;
|
||||
const userID = userState.userId;
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
@@ -51,7 +52,8 @@ const getPostByTitle = query(
|
||||
tags: [],
|
||||
userCommentArray: [],
|
||||
reactionArray: [],
|
||||
privilegeLevel: "anonymous" as const,
|
||||
isAuthenticated: false,
|
||||
isAdmin: false,
|
||||
userID: null
|
||||
};
|
||||
}
|
||||
@@ -77,13 +79,14 @@ const getPostByTitle = query(
|
||||
tags: [],
|
||||
userCommentArray: [],
|
||||
reactionArray: [],
|
||||
privilegeLevel: "anonymous" as const,
|
||||
isAuthenticated: false,
|
||||
isAdmin: false,
|
||||
userID: null
|
||||
};
|
||||
}
|
||||
|
||||
let query = "SELECT * FROM Post WHERE title = ?";
|
||||
if (privilegeLevel !== "admin") {
|
||||
if (!isAdmin) {
|
||||
query += ` AND published = TRUE`;
|
||||
}
|
||||
|
||||
@@ -110,7 +113,8 @@ const getPostByTitle = query(
|
||||
tags: [],
|
||||
userCommentArray: [],
|
||||
reactionArray: [],
|
||||
privilegeLevel: "anonymous" as const,
|
||||
isAuthenticated: false,
|
||||
isAdmin: false,
|
||||
userID: null
|
||||
};
|
||||
}
|
||||
@@ -123,14 +127,15 @@ const getPostByTitle = query(
|
||||
tags: [],
|
||||
userCommentArray: [],
|
||||
reactionArray: [],
|
||||
privilegeLevel: "anonymous" as const,
|
||||
isAuthenticated: false,
|
||||
isAdmin: false,
|
||||
userID: null
|
||||
};
|
||||
}
|
||||
|
||||
const conditionalContext = {
|
||||
isAuthenticated: userID !== null,
|
||||
privilegeLevel: privilegeLevel,
|
||||
isAdmin: isAdmin,
|
||||
userId: userID,
|
||||
currentDate: new Date(),
|
||||
featureFlags: getFeatureFlags(),
|
||||
@@ -245,7 +250,8 @@ const getPostByTitle = query(
|
||||
topLevelComments,
|
||||
userCommentArray,
|
||||
reactionArray,
|
||||
privilegeLevel,
|
||||
isAuthenticated,
|
||||
isAdmin,
|
||||
userID,
|
||||
sortBy,
|
||||
reads: post.reads || 0
|
||||
@@ -429,7 +435,7 @@ export default function PostPage() {
|
||||
<div>
|
||||
<SessionDependantLike
|
||||
currentUserID={postData.userID}
|
||||
privilegeLevel={postData.privilegeLevel}
|
||||
isAuthenticated={postData.isAuthenticated}
|
||||
likes={postData.likes as any[]}
|
||||
projectID={p().id}
|
||||
/>
|
||||
@@ -455,7 +461,8 @@ export default function PostPage() {
|
||||
class="mx-4 pt-12 pb-12 md:mx-8 lg:mx-12"
|
||||
>
|
||||
<CommentSectionWrapper
|
||||
privilegeLevel={postData.privilegeLevel}
|
||||
isAuthenticated={postData.isAuthenticated}
|
||||
isAdmin={postData.isAdmin}
|
||||
allComments={postData.comments as Comment[]}
|
||||
topLevelComments={
|
||||
postData.topLevelComments as Comment[]
|
||||
|
||||
@@ -13,7 +13,7 @@ const checkAdminAccess = query(async () => {
|
||||
// Reuse shared auth query for consistency
|
||||
const userState = await getUserState();
|
||||
|
||||
if (userState.privilegeLevel !== "admin") {
|
||||
if (!userState.isAdmin) {
|
||||
throw redirect("/401");
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const getPostForEdit = query(async (id: string) => {
|
||||
const { ConnectionFactory } = await import("~/server/utils");
|
||||
const userState = await getUserState();
|
||||
|
||||
if (userState.privilegeLevel !== "admin") {
|
||||
if (!userState.isAdmin) {
|
||||
throw redirect("/401");
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ const getPostForEdit = query(async (id: string) => {
|
||||
return {
|
||||
post,
|
||||
tags,
|
||||
privilegeLevel: userState.privilegeLevel,
|
||||
isAdmin: userState.isAdmin,
|
||||
userID: userState.userId
|
||||
};
|
||||
}, "post-for-edit");
|
||||
|
||||
@@ -15,10 +15,10 @@ const getPosts = query(async () => {
|
||||
const { ConnectionFactory } = await import("~/server/utils");
|
||||
const { withCacheAndStale } = await import("~/server/cache");
|
||||
const userState = await getUserState();
|
||||
const privilegeLevel = userState.privilegeLevel;
|
||||
const isAdmin = userState.isAdmin;
|
||||
|
||||
return withCacheAndStale(
|
||||
`posts-${privilegeLevel}`,
|
||||
`posts-${isAdmin ? "admin" : "user"}`,
|
||||
CACHE_CONFIG.BLOG_POSTS_LIST_CACHE_TTL_MS,
|
||||
async () => {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -43,7 +43,7 @@ const getPosts = query(async () => {
|
||||
LEFT JOIN Comment c ON p.id = c.post_id
|
||||
`;
|
||||
|
||||
if (privilegeLevel !== "admin") {
|
||||
if (!isAdmin) {
|
||||
postsQuery += ` WHERE p.published = TRUE`;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ const getPosts = query(async () => {
|
||||
SELECT t.value, t.post_id
|
||||
FROM Tag t
|
||||
JOIN Post p ON t.post_id = p.id
|
||||
${privilegeLevel !== "admin" ? "WHERE p.published = TRUE" : ""}
|
||||
${!isAdmin ? "WHERE p.published = TRUE" : ""}
|
||||
ORDER BY t.value ASC
|
||||
`;
|
||||
|
||||
@@ -70,7 +70,7 @@ const getPosts = query(async () => {
|
||||
tagMap[key] = (tagMap[key] || 0) + 1;
|
||||
});
|
||||
|
||||
return { posts, tags, tagMap, privilegeLevel };
|
||||
return { posts, tags, tagMap, isAdmin };
|
||||
}
|
||||
);
|
||||
}, "posts");
|
||||
@@ -106,11 +106,11 @@ export default function BlogIndex() {
|
||||
<TagSelector tagMap={loadedData().tagMap} />
|
||||
</Show>
|
||||
|
||||
<Show when={loadedData().privilegeLevel === "admin"}>
|
||||
<Show when={loadedData().isAdmin}>
|
||||
<PublishStatusToggle />
|
||||
</Show>
|
||||
|
||||
<Show when={loadedData().privilegeLevel === "admin"}>
|
||||
<Show when={loadedData().isAdmin}>
|
||||
<div class="mt-2 flex justify-center md:mt-0 md:justify-end">
|
||||
<A
|
||||
href="/blog/create"
|
||||
@@ -130,7 +130,7 @@ export default function BlogIndex() {
|
||||
<PostSorting
|
||||
posts={loadedData().posts}
|
||||
tags={loadedData().tags}
|
||||
privilegeLevel={loadedData().privilegeLevel}
|
||||
isAdmin={loadedData().isAdmin}
|
||||
filters={filters()}
|
||||
sort={sort()}
|
||||
include={include()}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getUserState } from "~/lib/auth-query";
|
||||
const checkAdminAccess = query(async () => {
|
||||
"use server";
|
||||
const userState = await getUserState();
|
||||
return { privilegeLevel: userState.privilegeLevel };
|
||||
return { isAdmin: userState.isAdmin };
|
||||
}, "test-auth-state");
|
||||
|
||||
type EndpointTest = {
|
||||
@@ -916,7 +916,7 @@ export default function TestPage() {
|
||||
description="tRPC API testing dashboard for developers to test endpoints and verify functionality."
|
||||
/>
|
||||
<Show
|
||||
when={authState()?.privilegeLevel === "admin"}
|
||||
when={authState()?.isAdmin}
|
||||
fallback={
|
||||
<div class="w-full pt-[30vh] text-center">
|
||||
<div class="text-text text-2xl">Unauthorized</div>
|
||||
|
||||
@@ -306,14 +306,11 @@ export const authRouter = createTRPCRouter({
|
||||
}
|
||||
}
|
||||
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
await createAuthSession(
|
||||
getH3Event(ctx),
|
||||
userId,
|
||||
isAdmin,
|
||||
true, // OAuth defaults to remember
|
||||
clientIP,
|
||||
userAgent
|
||||
@@ -521,15 +518,12 @@ export const authRouter = createTRPCRouter({
|
||||
}
|
||||
}
|
||||
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
// Create session with Vinxi (OAuth defaults to remember me)
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
await createAuthSession(
|
||||
getH3Event(ctx),
|
||||
userId,
|
||||
isAdmin,
|
||||
true, // OAuth defaults to remember
|
||||
clientIP,
|
||||
userAgent
|
||||
@@ -647,7 +641,6 @@ export const authRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
const userId = (res.rows[0] as unknown as User).id;
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
@@ -655,7 +648,6 @@ export const authRouter = createTRPCRouter({
|
||||
await createAuthSession(
|
||||
getH3Event(ctx),
|
||||
userId,
|
||||
isAdmin,
|
||||
rememberMe,
|
||||
clientIP,
|
||||
userAgent
|
||||
@@ -780,7 +772,6 @@ export const authRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
const userId = (res.rows[0] as unknown as User).id;
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
// Use rememberMe from JWT if not provided in input, default to false
|
||||
const shouldRemember =
|
||||
@@ -791,7 +782,6 @@ export const authRouter = createTRPCRouter({
|
||||
await createAuthSession(
|
||||
getH3Event(ctx),
|
||||
userId,
|
||||
isAdmin,
|
||||
shouldRemember,
|
||||
clientIP,
|
||||
userAgent
|
||||
@@ -983,12 +973,10 @@ export const authRouter = createTRPCRouter({
|
||||
// Create session with client info
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
await createAuthSession(
|
||||
getH3Event(ctx),
|
||||
userId,
|
||||
isAdmin,
|
||||
true, // Always use persistent sessions
|
||||
clientIP,
|
||||
userAgent
|
||||
@@ -1150,14 +1138,11 @@ export const authRouter = createTRPCRouter({
|
||||
// Reset rate limits on successful login
|
||||
await resetLoginRateLimits(email, clientIP);
|
||||
|
||||
const isAdmin = user.id === env.ADMIN_ID;
|
||||
|
||||
// Create session with Vinxi
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
await createAuthSession(
|
||||
getH3Event(ctx),
|
||||
user.id,
|
||||
isAdmin,
|
||||
rememberMe ?? false, // Default to session cookie (expires on browser close)
|
||||
clientIP,
|
||||
userAgent
|
||||
|
||||
@@ -7,9 +7,9 @@ import { CACHE_CONFIG } from "~/config";
|
||||
|
||||
const BLOG_CACHE_TTL = CACHE_CONFIG.BLOG_CACHE_TTL_MS;
|
||||
|
||||
const getAllPostsData = async (privilegeLevel: string) => {
|
||||
const getAllPostsData = async (isAdmin: boolean) => {
|
||||
return withCacheAndStale(
|
||||
`blog-posts-${privilegeLevel}`,
|
||||
`blog-posts-${isAdmin ? "admin" : "public"}`,
|
||||
BLOG_CACHE_TTL,
|
||||
async () => {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -34,7 +34,7 @@ const getAllPostsData = async (privilegeLevel: string) => {
|
||||
LEFT JOIN Comment c ON p.id = c.post_id
|
||||
`;
|
||||
|
||||
if (privilegeLevel !== "admin") {
|
||||
if (!isAdmin) {
|
||||
postsQuery += ` WHERE p.published = TRUE`;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ const getAllPostsData = async (privilegeLevel: string) => {
|
||||
SELECT t.value, t.post_id
|
||||
FROM Tag t
|
||||
JOIN Post p ON t.post_id = p.id
|
||||
${privilegeLevel !== "admin" ? "WHERE p.published = TRUE" : ""}
|
||||
${!isAdmin ? "WHERE p.published = TRUE" : ""}
|
||||
ORDER BY t.value ASC
|
||||
`;
|
||||
|
||||
@@ -64,21 +64,21 @@ const getAllPostsData = async (privilegeLevel: string) => {
|
||||
tagMap[key] = (tagMap[key] || 0) + 1;
|
||||
});
|
||||
|
||||
return { posts, tags, tagMap, privilegeLevel };
|
||||
return { posts, tags, tagMap, isAdmin };
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const blogRouter = createTRPCRouter({
|
||||
getRecentPosts: publicProcedure.query(async ({ ctx }) => {
|
||||
const allPostsData = await getAllPostsData("public");
|
||||
const allPostsData = await getAllPostsData(false);
|
||||
|
||||
return allPostsData.posts.slice(0, 3);
|
||||
}),
|
||||
|
||||
getPosts: publicProcedure.query(async ({ ctx }) => {
|
||||
const privilegeLevel = ctx.privilegeLevel;
|
||||
return getAllPostsData(privilegeLevel);
|
||||
const isAdmin = ctx.isAdmin;
|
||||
return getAllPostsData(isAdmin);
|
||||
}),
|
||||
|
||||
incrementPostRead: publicProcedure
|
||||
|
||||
@@ -144,7 +144,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
commentID: input.commentID,
|
||||
deletionType: input.deletionType,
|
||||
userId: ctx.userId,
|
||||
privilegeLevel: ctx.privilegeLevel
|
||||
isAdmin: ctx.isAdmin
|
||||
});
|
||||
|
||||
const commentQuery = await conn.execute({
|
||||
@@ -161,7 +161,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
const isOwner = comment.commenter_id === ctx.userId;
|
||||
const isAdmin = ctx.privilegeLevel === "admin";
|
||||
const isAdmin = ctx.isAdmin;
|
||||
|
||||
console.log("[deleteComment] Authorization check:", {
|
||||
isOwner,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { env } from "~/env/server";
|
||||
|
||||
export const infillRouter = createTRPCRouter({
|
||||
getConfig: publicProcedure.query(({ ctx }) => {
|
||||
if (ctx.privilegeLevel !== "admin") {
|
||||
if (!ctx.isAdmin) {
|
||||
return { endpoint: null, token: null };
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getAuthSession } from "~/server/session-helpers";
|
||||
export type Context = {
|
||||
event: APIEvent;
|
||||
userId: string | null;
|
||||
privilegeLevel: "anonymous" | "user" | "admin";
|
||||
isAdmin: boolean;
|
||||
};
|
||||
|
||||
async function createContextInner(event: APIEvent): Promise<Context> {
|
||||
@@ -16,11 +16,11 @@ async function createContextInner(event: APIEvent): Promise<Context> {
|
||||
const session = await getAuthSession(event.nativeEvent);
|
||||
|
||||
let userId: string | null = null;
|
||||
let privilegeLevel: "anonymous" | "user" | "admin" = "anonymous";
|
||||
let isAdmin = false;
|
||||
|
||||
if (session && session.userId) {
|
||||
userId = session.userId;
|
||||
privilegeLevel = session.isAdmin ? "admin" : "user";
|
||||
isAdmin = session.isAdmin;
|
||||
}
|
||||
|
||||
const req = event.nativeEvent.node?.req || event.nativeEvent;
|
||||
@@ -56,7 +56,7 @@ async function createContextInner(event: APIEvent): Promise<Context> {
|
||||
return {
|
||||
event,
|
||||
userId,
|
||||
privilegeLevel
|
||||
isAdmin
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export const createTRPCRouter = t.router;
|
||||
export const publicProcedure = t.procedure;
|
||||
|
||||
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
|
||||
if (!ctx.userId || ctx.privilegeLevel === "anonymous") {
|
||||
if (!ctx.userId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
|
||||
}
|
||||
return next({
|
||||
@@ -82,7 +82,7 @@ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
|
||||
});
|
||||
|
||||
const enforceUserIsAdmin = t.middleware(({ ctx, next }) => {
|
||||
if (ctx.privilegeLevel !== "admin") {
|
||||
if (!ctx.isAdmin) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Admin access required"
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getAuthSession } from "./session-helpers";
|
||||
|
||||
/**
|
||||
* Check authentication status
|
||||
* Consolidates getUserID, getPrivilegeLevel, and checkAuthStatus into single function
|
||||
* @param event - H3Event
|
||||
* @returns Object with isAuthenticated, userId, and isAdmin flags
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
describe("parseConditionals", () => {
|
||||
const baseContext: ConditionalContext = {
|
||||
isAuthenticated: true,
|
||||
privilegeLevel: "user",
|
||||
isAdmin: false,
|
||||
userId: "test-user",
|
||||
currentDate: new Date("2025-06-01"),
|
||||
featureFlags: { "beta-feature": true },
|
||||
@@ -34,7 +34,7 @@ describe("parseConditionals", () => {
|
||||
const anonContext: ConditionalContext = {
|
||||
...baseContext,
|
||||
isAuthenticated: false,
|
||||
privilegeLevel: "anonymous"
|
||||
isAdmin: false
|
||||
};
|
||||
const result = parseConditionals(html, anonContext);
|
||||
expect(result).not.toContain("Secret content");
|
||||
@@ -51,7 +51,7 @@ describe("parseConditionals", () => {
|
||||
|
||||
const adminContext: ConditionalContext = {
|
||||
...baseContext,
|
||||
privilegeLevel: "admin"
|
||||
isAdmin: true
|
||||
};
|
||||
const adminResult = parseConditionals(html, adminContext);
|
||||
expect(adminResult).toContain("Admin panel");
|
||||
@@ -119,7 +119,7 @@ describe("parseConditionals", () => {
|
||||
const anonContext: ConditionalContext = {
|
||||
...baseContext,
|
||||
isAuthenticated: false,
|
||||
privilegeLevel: "anonymous"
|
||||
isAdmin: false
|
||||
};
|
||||
const anonResult = parseConditionals(html, anonContext);
|
||||
expect(anonResult).toContain("Not authenticated content");
|
||||
|
||||
@@ -11,7 +11,7 @@ export function getSafeEnvVariables(): Record<string, string | undefined> {
|
||||
|
||||
export interface ConditionalContext {
|
||||
isAuthenticated: boolean;
|
||||
privilegeLevel: "admin" | "user" | "anonymous";
|
||||
isAdmin: boolean;
|
||||
userId: string | null;
|
||||
currentDate: Date;
|
||||
featureFlags: Record<string, boolean>;
|
||||
@@ -194,7 +194,16 @@ function evaluatePrivilegeCondition(
|
||||
value: string,
|
||||
context: ConditionalContext
|
||||
): boolean {
|
||||
return context.privilegeLevel === value;
|
||||
switch (value) {
|
||||
case "admin":
|
||||
return context.isAdmin;
|
||||
case "user":
|
||||
return context.isAuthenticated && !context.isAdmin;
|
||||
case "anonymous":
|
||||
return !context.isAuthenticated;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
import { ConnectionFactory } from "./database";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
|
||||
/**
|
||||
* Migration script to add multi-provider and enhanced session support
|
||||
* Run this script once to migrate existing database
|
||||
*/
|
||||
|
||||
export async function migrateMultiAuth() {
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
|
||||
try {
|
||||
// Step 1: Check if UserProvider table exists
|
||||
const tableCheck = await conn.execute({
|
||||
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name='UserProvider'"
|
||||
});
|
||||
|
||||
if (tableCheck.rows.length > 0) {
|
||||
|
||||
} else {
|
||||
|
||||
await conn.execute(`
|
||||
CREATE TABLE UserProvider (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
provider TEXT NOT NULL CHECK(provider IN ('email', 'google', 'github', 'apple')),
|
||||
provider_user_id TEXT,
|
||||
email TEXT,
|
||||
display_name TEXT,
|
||||
image TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_used_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
|
||||
await conn.execute(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_user_provider_provider_user ON UserProvider (provider, provider_user_id)"
|
||||
);
|
||||
await conn.execute(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_user_provider_provider_email ON UserProvider (provider, email)"
|
||||
);
|
||||
await conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_user_provider_user_id ON UserProvider (user_id)"
|
||||
);
|
||||
await conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_user_provider_provider ON UserProvider (provider)"
|
||||
);
|
||||
await conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_user_provider_email ON UserProvider (email)"
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Check if Session table has device columns
|
||||
const sessionColumnsCheck = await conn.execute({
|
||||
sql: "PRAGMA table_info(Session)"
|
||||
});
|
||||
const hasDeviceName = sessionColumnsCheck.rows.some(
|
||||
(row: any) => row.name === "device_name"
|
||||
);
|
||||
|
||||
if (hasDeviceName) {
|
||||
|
||||
} else {
|
||||
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN device_name TEXT");
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN device_type TEXT");
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN browser TEXT");
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN os TEXT");
|
||||
|
||||
// SQLite doesn't support non-constant defaults in ALTER TABLE
|
||||
// Add column with NULL default, then update existing rows
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN last_active_at TEXT");
|
||||
|
||||
// Update existing rows to set last_active_at = last_used
|
||||
|
||||
await conn.execute(
|
||||
"UPDATE Session SET last_active_at = COALESCE(last_used, created_at) WHERE last_active_at IS NULL"
|
||||
);
|
||||
|
||||
|
||||
await conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_session_last_active ON Session (last_active_at)"
|
||||
);
|
||||
await conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_session_user_active ON Session (user_id, revoked, last_active_at)"
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Migrate existing users to UserProvider table
|
||||
|
||||
const usersResult = await conn.execute({
|
||||
sql: "SELECT id, email, provider, display_name, image, apple_user_string FROM User WHERE provider IS NOT NULL"
|
||||
});
|
||||
|
||||
|
||||
|
||||
let migratedCount = 0;
|
||||
for (const row of usersResult.rows) {
|
||||
const user = row as any;
|
||||
|
||||
// Skip apple provider users (they're for Life and Lineage mobile app, not website auth)
|
||||
if (user.provider === "apple") {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if already migrated
|
||||
const existingProvider = await conn.execute({
|
||||
sql: "SELECT id FROM UserProvider WHERE user_id = ? AND provider = ?",
|
||||
args: [user.id, user.provider || "email"]
|
||||
});
|
||||
|
||||
if (existingProvider.rows.length > 0) {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine provider_user_id based on provider type
|
||||
let providerUserId: string | null = null;
|
||||
if (user.provider === "github") {
|
||||
providerUserId = user.display_name;
|
||||
} else if (user.provider === "google") {
|
||||
providerUserId = user.email;
|
||||
} else {
|
||||
providerUserId = user.email;
|
||||
}
|
||||
|
||||
try {
|
||||
await conn.execute({
|
||||
sql: `INSERT INTO UserProvider (id, user_id, provider, provider_user_id, email, display_name, image)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
args: [
|
||||
uuidV4(),
|
||||
user.id,
|
||||
user.provider || "email",
|
||||
providerUserId,
|
||||
user.email,
|
||||
user.display_name,
|
||||
user.image
|
||||
]
|
||||
});
|
||||
migratedCount++;
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`[Migration] Failed to migrate user ${user.id}:`,
|
||||
error.message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine provider_user_id based on provider type
|
||||
let providerUserId: string | null = null;
|
||||
if (user.provider === "github") {
|
||||
providerUserId = user.display_name;
|
||||
} else if (user.provider === "google") {
|
||||
providerUserId = user.email;
|
||||
} else if (user.provider === "apple") {
|
||||
providerUserId = user.apple_user_string;
|
||||
} else {
|
||||
providerUserId = user.email;
|
||||
}
|
||||
|
||||
try {
|
||||
await conn.execute({
|
||||
sql: `INSERT INTO UserProvider (id, user_id, provider, provider_user_id, email, display_name, image)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
args: [
|
||||
uuidV4(),
|
||||
user.id,
|
||||
user.provider || "email",
|
||||
providerUserId,
|
||||
user.email,
|
||||
user.display_name,
|
||||
user.image
|
||||
]
|
||||
});
|
||||
migratedCount++;
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`[Migration] Failed to migrate user ${user.id}:`,
|
||||
error.message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Step 4: Verification
|
||||
|
||||
const providerCount = await conn.execute({
|
||||
sql: "SELECT COUNT(*) as count FROM UserProvider"
|
||||
});
|
||||
|
||||
|
||||
const multiProviderUsers = await conn.execute({
|
||||
sql: `SELECT COUNT(*) as count FROM (
|
||||
SELECT user_id FROM UserProvider GROUP BY user_id HAVING COUNT(*) > 1
|
||||
)`
|
||||
});
|
||||
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
migratedUsers: migratedCount,
|
||||
totalProviders: (providerCount.rows[0] as any).count
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration if called directly
|
||||
if (require.main === module) {
|
||||
migrateMultiAuth()
|
||||
.then((result) => {
|
||||
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -90,7 +90,6 @@ export function hashRefreshToken(token: string): string {
|
||||
* Create a new session in database and Vinxi session
|
||||
* @param event - H3Event
|
||||
* @param userId - User ID
|
||||
* @param isAdmin - Whether user is admin
|
||||
* @param rememberMe - Whether to use extended session duration
|
||||
* @param ipAddress - Client IP address
|
||||
* @param userAgent - Client user agent string
|
||||
@@ -101,7 +100,6 @@ export function hashRefreshToken(token: string): string {
|
||||
export async function createAuthSession(
|
||||
event: H3Event,
|
||||
userId: string,
|
||||
isAdmin: boolean,
|
||||
rememberMe: boolean,
|
||||
ipAddress: string,
|
||||
userAgent: string,
|
||||
@@ -109,6 +107,19 @@ export async function createAuthSession(
|
||||
tokenFamily: string | null = null
|
||||
): Promise<SessionData> {
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
// Fetch is_admin from database
|
||||
const userResult = await conn.execute({
|
||||
sql: "SELECT is_admin FROM User WHERE id = ?",
|
||||
args: [userId]
|
||||
});
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
throw new Error(`User not found: ${userId}`);
|
||||
}
|
||||
|
||||
const isAdmin = userResult.rows[0].is_admin === 1;
|
||||
|
||||
const sessionId = uuidV4();
|
||||
const family = tokenFamily || uuidV4();
|
||||
const refreshToken = generateRefreshToken();
|
||||
@@ -374,10 +385,10 @@ async function restoreSessionFromDB(
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
// Query DB for session with all necessary data
|
||||
// Query DB for session with all necessary data including is_admin
|
||||
const result = await conn.execute({
|
||||
sql: `SELECT s.id, s.user_id, s.token_family, s.refresh_token_hash,
|
||||
s.revoked, s.expires_at, u.isAdmin
|
||||
s.revoked, s.expires_at, u.is_admin
|
||||
FROM Session s
|
||||
JOIN User u ON s.user_id = u.id
|
||||
WHERE s.id = ?`,
|
||||
@@ -412,7 +423,6 @@ async function restoreSessionFromDB(
|
||||
const newSession = await createAuthSession(
|
||||
event,
|
||||
dbSession.user_id as string,
|
||||
dbSession.isAdmin === 1,
|
||||
true, // Assume rememberMe=true for restoration
|
||||
ipAddress,
|
||||
userAgent,
|
||||
@@ -678,7 +688,6 @@ export async function rotateAuthSession(
|
||||
const newSessionData = await createAuthSession(
|
||||
event,
|
||||
oldSessionData.userId,
|
||||
oldSessionData.isAdmin,
|
||||
oldSessionData.rememberMe,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
|
||||
@@ -55,8 +55,6 @@ export interface BackupResponse {
|
||||
commentParent?: number | null;
|
||||
}
|
||||
|
||||
export type PrivilegeLevel = "admin" | "user" | "anonymous";
|
||||
|
||||
export type SortingMode = "newest" | "oldest" | "highest_rated" | "hot";
|
||||
|
||||
export type DeletionType = "user" | "admin" | "database";
|
||||
@@ -64,7 +62,8 @@ export type DeletionType = "user" | "admin" | "database";
|
||||
export type ModificationType = "delete" | "edit";
|
||||
|
||||
export interface CommentSectionWrapperProps {
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
isAuthenticated: boolean;
|
||||
isAdmin: boolean;
|
||||
allComments: Comment[];
|
||||
topLevelComments: Comment[];
|
||||
id: number;
|
||||
@@ -74,7 +73,8 @@ export interface CommentSectionWrapperProps {
|
||||
}
|
||||
|
||||
export interface CommentSectionProps {
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
isAuthenticated: boolean;
|
||||
isAdmin: boolean;
|
||||
postID: number;
|
||||
allComments: Comment[];
|
||||
topLevelComments: Comment[];
|
||||
@@ -101,7 +101,8 @@ export interface CommentBlockProps {
|
||||
recursionCount: number;
|
||||
allComments: Comment[] | undefined;
|
||||
child_comments: Comment[] | undefined;
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
isAuthenticated: boolean;
|
||||
isAdmin: boolean;
|
||||
currentUserID: string;
|
||||
reactionMap: Map<number, CommentReaction[]>;
|
||||
level: number;
|
||||
@@ -124,7 +125,7 @@ export interface CommentBlockProps {
|
||||
export interface CommentInputBlockProps {
|
||||
isReply: boolean;
|
||||
parent_id?: number;
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
isAuthenticated: boolean;
|
||||
post_id: number;
|
||||
socket: WebSocket | undefined;
|
||||
currentUserID: string;
|
||||
@@ -134,7 +135,8 @@ export interface CommentInputBlockProps {
|
||||
|
||||
export interface CommentSortingProps {
|
||||
topLevelComments: Comment[];
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
isAuthenticated: boolean;
|
||||
isAdmin: boolean;
|
||||
postID: number;
|
||||
allComments: Comment[];
|
||||
reactionMap: Map<number, CommentReaction[]>;
|
||||
@@ -170,14 +172,14 @@ export interface ReactionBarProps {
|
||||
currentUserID: string;
|
||||
commentID: number;
|
||||
reactions: CommentReaction[];
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
isAuthenticated: boolean;
|
||||
showingReactionOptions: boolean;
|
||||
commentReaction: (reactionType: ReactionType, commentID: number) => void;
|
||||
}
|
||||
|
||||
export interface CommentDeletionPromptProps {
|
||||
isOpen: boolean;
|
||||
privilegeLevel: PrivilegeLevel;
|
||||
isAdmin: boolean;
|
||||
commentID: number;
|
||||
commenterID: string;
|
||||
deleteComment: (
|
||||
|
||||
Reference in New Issue
Block a user