Skip to content

Commit

Permalink
Merge pull request #530 from sinamics/deviceid
Browse files Browse the repository at this point in the history
Improved device id hash generation
  • Loading branch information
sinamics authored Aug 29, 2024
2 parents 8e9a9d6 + 153ae0e commit f85ab6d
Show file tree
Hide file tree
Showing 19 changed files with 197 additions and 177 deletions.
17 changes: 14 additions & 3 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"axios": "^1.7.4",
"bcryptjs": "^2.4.3",
"classnames": "^2.3.2",
"cookie": "^0.6.0",
"cron": "^3.1.5",
"daisyui": "^4.12.10",
"ejs": "^3.1.10",
Expand Down
4 changes: 3 additions & 1 deletion src/components/adminPage/users/userGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ type GroupLabelProps = {
};

const GroupLabel = ({ groups }: GroupLabelProps) => {
if (!Array.isArray(groups) || !groups) return null;
const t = useTranslations("admin");
const m = useTranslations("commonToast");

Expand All @@ -45,6 +44,9 @@ const GroupLabel = ({ groups }: GroupLabelProps) => {
toastMessage: m("deletedSuccessfully"),
}),
});

if (!Array.isArray(groups) || !groups) return null;

return (
<div className="flex flex-wrap gap-3 text-center">
{groups?.map((group) => {
Expand Down
19 changes: 10 additions & 9 deletions src/components/auth/userDevices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import Monitor from "~/icons/monitor";
import Tablet from "~/icons/tablet";
import { api } from "~/utils/api";
import { useTranslations } from "next-intl";
import { signOut } from "next-auth/react";
import { generateDeviceId, parseUA } from "~/utils/devices";
import { signOut, useSession } from "next-auth/react";

const formatLastActive = (date) => {
return new Date(date).toLocaleString("no-NO");
Expand Down Expand Up @@ -49,8 +48,9 @@ const DeviceIcon = ({ deviceType }: { deviceType: string }) => {

const ListUserDevices: React.FC<{ devices: UserDevice[] }> = ({ devices }) => {
const t = useTranslations();
const { data: me, refetch } = api.auth.me.useQuery();

const { refetch } = api.auth.me.useQuery();
const { data: session } = useSession();
const { mutate: deleteUserDevice, isLoading: deleteLoading } =
api.auth.deleteUserDevice.useMutation({
onSuccess: () => {
Expand All @@ -59,18 +59,19 @@ const ListUserDevices: React.FC<{ devices: UserDevice[] }> = ({ devices }) => {
},
});

const currentDeviceId = session?.user?.deviceId;

const isCurrentDevice = (device: UserDevice) => {
return (
device?.deviceId === generateDeviceId(parseUA(navigator.userAgent), me.id)?.deviceId
);
return device?.deviceId === currentDeviceId;
};

// sort devices, current device first
devices?.sort((a, b) => {
const sortedDevices = [...(devices || [])].sort((a, b) => {
if (isCurrentDevice(a)) return -1;
if (isCurrentDevice(b)) return 1;
return 0;
});

return (
<div className="mx-auto">
<div className="flex justify-between">
Expand All @@ -80,8 +81,8 @@ const ListUserDevices: React.FC<{ devices: UserDevice[] }> = ({ devices }) => {
{/* <button className="btn btn-sm btn-error btn-outline">Logout All</button> */}
</div>
<div className="space-y-2 max-h-[500px] overflow-auto custom-scrollbar">
{devices && devices.length > 0 ? (
devices.map((device) => (
{sortedDevices && sortedDevices.length > 0 ? (
sortedDevices.map((device) => (
<div
key={device.id}
className={cn(
Expand Down
4 changes: 3 additions & 1 deletion src/components/userSettings/apiToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useTranslations } from "next-intl";
import { AuthorizationType } from "~/types/apiTypes";

const ApiLables = ({ tokens }) => {
if (!Array.isArray(tokens) || !tokens) return null;
const t = useTranslations("userSettings");

const { refetch } = api.auth.getApiToken.useQuery();
Expand All @@ -34,6 +33,9 @@ const ApiLables = ({ tokens }) => {
refetch();
},
});

if (!Array.isArray(tokens) || !tokens) return null;

return (
<div className="flex flex-wrap gap-3 text-center">
{tokens?.map((token) => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import NextAuth from "next-auth";
import { getAuthOptions } from "~/server/auth";

export default async function auth(req: NextApiRequest, res: NextApiResponse) {
const authOptions = getAuthOptions(req);
const authOptions = getAuthOptions(req, res);

return await NextAuth(req, res, authOptions);
}
2 changes: 1 addition & 1 deletion src/pages/api/auth/two-factor/totp/disable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(405).json({ message: "Method not allowed" });
}

const session = await getServerSession(req, res, getAuthOptions(req));
const session = await getServerSession(req, res, getAuthOptions(req, res));
if (!session) {
return res.status(401).json({ message: "Not authenticated" });
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/auth/two-factor/totp/enable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(405).json({ message: "Method not allowed" });
}

const session = await getServerSession(req, res, getAuthOptions(req));
const session = await getServerSession(req, res, getAuthOptions(req, res));
if (!session) {
return res.status(401).json({ message: "Not authenticated" });
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/auth/two-factor/totp/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(405).json({ message: "Method not allowed" });
}

const session = await getServerSession(req, res, getAuthOptions(req));
const session = await getServerSession(req, res, getAuthOptions(req, res));
if (!session) {
return res.status(401).json({ error: ErrorCode.InternalServerError });
}
Expand Down
6 changes: 5 additions & 1 deletion src/pages/api/auth/user/invalidateUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export default async function handler(
}

try {
const session = await getServerSession(request, response, getAuthOptions(request));
const session = await getServerSession(
request,
response,
getAuthOptions(request, response),
);
if (!session) {
return response.status(401).json({ message: "Not authenticated" });
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/mkworld/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const config = {
};

export default async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, getAuthOptions(req));
const session = await getServerSession(req, res, getAuthOptions(req, res));
if (!session) {
res.status(401).json({ message: "Authorization Error" });
return;
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/websocket/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface SocketIoExtension {

export type NextApiResponseWithSocketIo = NextApiResponse & SocketIoExtension;
const SocketHandler = async (req: NextApiRequest, res: NextApiResponseWithSocketIo) => {
const session = await getServerSession(req, res, getAuthOptions(req));
const session = await getServerSession(req, res, getAuthOptions(req, res));
if (!session) {
res.status(401).json({ message: "Authorization Error" });
return;
Expand Down
30 changes: 14 additions & 16 deletions src/pages/user-settings/network/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,20 @@ const UserNetworkSetting = () => {
<div className="flex justify-between">
<div>
<p className="font-medium">Enable global node naming</p>
<p className="text-sm text-gray-500">
When enabled, this feature will:
<ul className="list-disc list-inside mt-2">
<li>
Maintain a consistent name for each node across all networks you manage.
</li>
<li>
Update the node's name in all your networks when you rename it in one
network.
</li>
<li>
Upon member / node registration, check if the member exists in your
other networks and use the first name found.
</li>
</ul>
</p>
<p className="text-sm text-gray-500">When enabled, this feature will:</p>
<ul className="list-disc list-inside mt-2 text-sm text-gray-500">
<li>
Maintain a consistent name for each node across all networks you manage.
</li>
<li>
Update the node's name in all your networks when you rename it in one
network.
</li>
<li>
Upon member / node registration, check if the member exists in your other
networks and use the first name found.
</li>
</ul>
<p className="mt-2 text-sm text-gray-500">
Note: This feature has priority over "Add Member ID as Name". It applies
only to networks where you are the author and doesn't affect networks
Expand Down
10 changes: 6 additions & 4 deletions src/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ interface IUserAgent {

declare module "next-auth" {
interface Session extends DefaultSession {
user: IUser;
user: IUser & { deviceId: string };
error: string;
// userAgent?: IUserAgent | unknown;
// deviceId?: string;
Expand Down Expand Up @@ -105,6 +105,7 @@ const genericOAuthAuthorization = buildAuthorizationConfig(
*/
export const getAuthOptions = (
req: GetServerSidePropsContext["req"],
res: GetServerSidePropsContext["res"],
): NextAuthOptions => ({
adapter: MyAdapter,
providers: [
Expand All @@ -130,6 +131,7 @@ export const getAuthOptions = (
id: profile.sub || profile.id.toString(),
name: profile.name || profile.login || profile.username,
email: profile.email,
deviceId: null,
// image: profile.picture || profile.avatar_url || profile.image_url,
lastLogin: new Date().toISOString(),
role: "USER",
Expand Down Expand Up @@ -278,8 +280,8 @@ export const getAuthOptions = (
/**
* @see https://next-auth.js.org/configuration/callbacks#sign-in-callback
*/
signIn: signInCallback(req),
jwt: jwtCallback(req),
signIn: signInCallback(req, res),
jwt: jwtCallback(),
session: sessionCallback(req),
redirect({ url, baseUrl }) {
// Allows relative callback URLs
Expand All @@ -304,5 +306,5 @@ export const getServerAuthSession = (ctx: {
req: GetServerSidePropsContext["req"];
res: GetServerSidePropsContext["res"];
}) => {
return getServerSession(ctx.req, ctx.res, getAuthOptions(ctx.req));
return getServerSession(ctx.req, ctx.res, getAuthOptions(ctx.req, ctx.res));
};
19 changes: 7 additions & 12 deletions src/server/callbacks/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { IncomingMessage } from "http";
import { prisma } from "../db";
import { generateDeviceId, parseUA } from "~/utils/devices";

export function jwtCallback(
req: IncomingMessage & { cookies: Partial<{ [key: string]: string }> },
) {
return async function jwt({ token, user, trigger, account, session }) {
export function jwtCallback() {
return async function jwt({ token, user, trigger, account, session, profile }) {
// console.log(user);

if (trigger === "update") {
if (session.update) {
const updateObject: Record<string, string | Date> = {};
Expand Down Expand Up @@ -60,12 +58,9 @@ export function jwtCallback(
if (user) {
const { id, name, email, role } = user;
Object.assign(token, { id, name, email, role });

if (account?.provider === "oauth") {
const userAgent = req.headers["user-agent"];
const { deviceId } = generateDeviceId(parseUA(userAgent), id);
token.deviceId = deviceId;

// set the device from sign in callback
token.deviceId = profile.deviceId;
token.accessToken = account.accessToken;
} else if (account?.provider === "credentials") {
token.deviceId = user.deviceId;
Expand All @@ -83,7 +78,7 @@ export function jwtCallback(
});

if (!userDevice) {
// Device doesn't exist, invalidate the token
// Device doesn't exist, invalidate the deviceId in the token
token.deviceId = undefined;
return token;
}
Expand Down
1 change: 0 additions & 1 deletion src/server/callbacks/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export function sessionCallback(
...session,
user: {
...token,
userAgent: token.userAgent,
},
};
};
Expand Down
Loading

0 comments on commit f85ab6d

Please sign in to comment.