Skip to content

Commit

Permalink
Merge pull request #119 from siinghd/session
Browse files Browse the repository at this point in the history
FEAT: single session/login
  • Loading branch information
hkirat authored Feb 26, 2024
2 parents 4f2c2b7 + 43cb7ec commit ea852bf
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 3 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ BOT_TOKEN = "123"
GUILD_ID = "123"
LOCAL_CMS_PROVIDER = true
CACHE_EXPIRE_S = 10

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"dayjs": "^1.11.10",
"discord-oauth2": "^2.11.0",
"discord.js": "^14.14.1",
"jose": "^5.2.2",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.321.0",
"next": "14.0.2",
Expand Down
9 changes: 6 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ model User {
id String @id @default(cuid())
name String?
email String? @unique
token String?
sessions Session[]
purchases UserPurchases[]
videoProgress VideoProgress[]
Expand Down
27 changes: 27 additions & 0 deletions src/actions/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use server';
import db from '@/db';

export const logoutUser = async (email: string, adminPassword: string) => {
if (adminPassword !== process.env.ADMIN_SECRET) {
return { error: 'Unauthorized' };
}

const user = await db.user.findFirst({
where: {
email,
},
});
if (!user) {
return { message: 'User not found' };
}
await db.user.update({
where: {
id: user.id,
},
data: {
token: '',
},
});

return { message: 'User logged out' };
};
63 changes: 63 additions & 0 deletions src/app/admin/user/LogoutUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';
import { logoutUser } from '@/actions/user';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@radix-ui/react-dropdown-menu';
import React from 'react';
import { toast } from 'sonner';

const LogoutUserComp = () => {
const formRef = React.useRef<HTMLFormElement>(null);

const handlLogout = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);

const email = formData.get('email') as string;
const adminPassword = formData.get('adminPassword') as string;
const res = await logoutUser(email, adminPassword);
toast.info(res.message);
};
return (
<form onSubmit={handlLogout} ref={formRef}>
<div className="w-full max-w-sm rounded-lg border border-gray-200 grid grid-rows-5 shadow-sm dark:border-gray-800">
<div className="p-6 grid gap-2 items-center row-span-3">
<div className="text-3xl font-bold">Logout the user</div>
<div className="text-sm font-medium leading-none text-gray-500 dark:text-gray-400">
Enter the information below to logout the user
</div>
</div>

<div className="p-6 flex items-center row-span-2">
<Label className="sr-only">EMAIL</Label>
<Input
className="w-full"
id="email"
name="email"
placeholder="[email protected]"
style={{
minWidth: '0',
}}
/>
</div>
<div className="p-6 flex items-center row-span-2">
<Label className="sr-only">Admin password</Label>
<Input
className="w-full"
id="adminPassword"
name="adminPassword"
placeholder="Admin password"
style={{
minWidth: '0',
}}
/>
</div>
<div className="p-6 flex items-center justify-center row-span-2">
<Button className="w-full">Logout</Button>
</div>
</div>
</form>
);
};

export default LogoutUserComp;
12 changes: 12 additions & 0 deletions src/app/admin/user/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import LogoutUserComp from './LogoutUser';

const UserAdminPage = () => {
return (
<div className="flex justify-center h-[100dvh] items-center">
<LogoutUserComp />
</div>
);
};

export default UserAdminPage;
15 changes: 15 additions & 0 deletions src/app/api/user/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type NextRequest, NextResponse } from 'next/server';
import db from '@/db';

export async function GET(req: NextRequest) {
const url = new URL(req.url);
const token = url.searchParams.get('token');
const user = await db.user.findFirst({
where: {
token,
},
});
return NextResponse.json({
user,
});
}
28 changes: 28 additions & 0 deletions src/app/invalidsession/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { signOut } from 'next-auth/react';
import React, { useEffect } from 'react';
import { Toaster } from '@/components/ui/sonner';
import { toast } from 'sonner';

const page = () => {
useEffect(() => {
signOut({
callbackUrl: '/signin',
});
toast('Too many devices connected. Logging out!', {
action: {
label: 'Close',
onClick: () => console.log('Closed Toast'),
},
});
}, []);

return (
<div>
<Toaster />
</div>
);
};

export default page;
23 changes: 23 additions & 0 deletions src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import db from '@/db';
import CredentialsProvider from 'next-auth/providers/credentials';
import { SignJWT, importJWK } from 'jose';

const generateJWT = async (payload: any) => {
const secret = process.env.JWT_SECRET || 'secret';

const jwk = await importJWK({ k: secret, alg: 'HS256', kty: 'oct' });

const jwt = await new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('365d')
.sign(jwk);

return jwt;
};
async function validateUser(
email: string,
password: string,
Expand Down Expand Up @@ -71,6 +85,9 @@ export const authOptions = {
credentials.password,
);
if (user.data) {
const jwt = await generateJWT({
id: user.data.userid,
});
try {
await db.user.upsert({
where: {
Expand All @@ -80,11 +97,13 @@ export const authOptions = {
id: user.data.userid,
name: user.data.name,
email: credentials.username,
token: jwt,
},
update: {
id: user.data.userid,
name: user.data.name,
email: credentials.username,
token: jwt,
},
});
} catch (e) {
Expand All @@ -95,6 +114,7 @@ export const authOptions = {
id: user.data.userid,
name: user.data.name,
email: credentials.username,
token: jwt,
};
}
// Return null if user data could not be retrieved
Expand All @@ -111,12 +131,15 @@ export const authOptions = {
session: async ({ session, token }: any) => {
if (session?.user) {
session.user.id = token.uid;
session.user.jwtToken = token.jwtToken;
}

return session;
},
jwt: async ({ user, token }: any) => {
if (user) {
token.uid = user.id;
token.jwtToken = user.token;
}
return token;
},
Expand Down
21 changes: 21 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { withAuth } from 'next-auth/middleware';
import { NextResponse } from 'next/server';

export const config = {
matcher: ['/courses/:path*'],
};

export default withAuth(async (req) => {
const token = req.nextauth.token;
if (!token) {
return NextResponse.redirect(new URL('/invalidsession', req.url));
}
const user = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL_LOCAL}/api/user?token=${token.jwtToken}`,
);

const json = await user.json();
if (!json.user) {
return NextResponse.redirect(new URL('/invalidsession', req.url));
}
});

0 comments on commit ea852bf

Please sign in to comment.