-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathauthUser.ts
117 lines (95 loc) · 4.13 KB
/
authUser.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import middy from "@middy/core";
import jsonBodyParser from "@middy/http-json-body-parser";
import { createClient } from "@supabase/supabase-js";
import { ethers } from "ethers";
import * as jwt from "jose";
import { SiweMessage } from "siwe";
import { ETH_SIGNATURE_REGEX, DEFAULT_CHAIN, isProductionDeployment } from "consts/processEnvConsts";
import { netlifyUri, netlifyDeployUri, netlifyDeployPrimeUri } from "src/generatedNetlifyInfo.json";
import { Database } from "src/types/supabase-notification";
const authUser = async (event) => {
try {
if (!event.body) {
throw new Error("No body provided");
}
const signature = event?.body?.signature;
if (!signature) {
throw new Error("Missing key : signature");
}
if (!ETH_SIGNATURE_REGEX.test(signature)) {
throw new Error("Invalid signature");
}
const message = event?.body?.message;
if (!message) {
throw new Error("Missing key : message");
}
const address = event?.body?.address;
if (!address) {
throw new Error("Missing key : address");
}
const siweMessage = new SiweMessage(message);
if (
!(
(netlifyUri && netlifyUri === siweMessage.uri) ||
(netlifyDeployUri && netlifyDeployUri === siweMessage.uri) ||
(netlifyDeployPrimeUri && netlifyDeployPrimeUri === siweMessage.uri)
)
) {
console.debug(
`Invalid URI: expected one of [${netlifyUri} ${netlifyDeployUri} ${netlifyDeployPrimeUri}] but got ${siweMessage.uri}`
);
throw new Error(`Invalid URI`);
}
if (siweMessage.chainId !== DEFAULT_CHAIN) {
console.debug(`Invalid chain ID: expected ${DEFAULT_CHAIN} but got ${siweMessage.chainId}`);
throw new Error(`Invalid chain ID`);
}
const lowerCaseAddress = siweMessage.address.toLowerCase();
if (lowerCaseAddress !== address.toLowerCase()) {
throw new Error("Address mismatch in provided address and message");
}
const supabase = createClient<Database>(process.env.SUPABASE_URL!, process.env.SUPABASE_CLIENT_API_KEY!);
// get nonce from db, if its null that means it was already used
const { error: nonceError, data: nonceData } = await supabase
.from("user-nonce")
.select("nonce")
.eq("address", lowerCaseAddress)
.single();
if (nonceError || !nonceData?.nonce) {
throw new Error("Unable to fetch nonce from DB");
}
try {
// If the main Alchemy API key is permissioned, it won't work in a Netlify Function so we use a dedicated API key
const alchemyApiKey = process.env.ALCHEMY_FUNCTIONS_API_KEY ?? process.env.ALCHEMY_API_KEY;
const alchemyChain = isProductionDeployment() ? "arb-mainnet" : "arb-sepolia";
const alchemyRpcURL = `https://${alchemyChain}.g.alchemy.com/v2/${alchemyApiKey}`;
const provider = new ethers.providers.JsonRpcProvider(alchemyRpcURL);
await siweMessage.verify({ signature, nonce: nonceData.nonce, time: new Date().toISOString() }, { provider });
} catch (err) {
throw new Error("Invalid signer: " + JSON.stringify(err));
}
const { error } = await supabase.from("user-nonce").delete().match({ address: lowerCaseAddress });
if (error) {
throw new Error("Error updating nonce in DB");
}
const issuer = process.env.JWT_ISSUER ?? "Kleros"; // ex :- Kleros
const audience = process.env.JWT_AUDIENCE ?? "Court"; // ex :- Court, Curate, Escrow
const authExp = process.env.JWT_EXP_TIME ?? "2h";
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error("Secret not set in environment");
}
// user verified, generate auth token
const encodedSecret = new TextEncoder().encode(secret);
const token = await new jwt.SignJWT({ id: address.toLowerCase() })
.setProtectedHeader({ alg: "HS256" })
.setIssuer(issuer)
.setAudience(audience)
.setExpirationTime(authExp)
.sign(encodedSecret);
return { statusCode: 200, body: JSON.stringify({ message: "User authorised", token }) };
} catch (err) {
return { statusCode: 500, body: JSON.stringify({ message: `${err}` }) };
}
};
export const handler = middy(authUser).use(jsonBodyParser());