Skip to content

Commit 69a921d

Browse files
authored
tw:login (#6122)
1 parent 9abdb92 commit 69a921d

File tree

18 files changed

+1133
-14
lines changed

18 files changed

+1133
-14
lines changed

apps/login/.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts

apps/login/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

apps/login/eslint.config.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { dirname } from "node:path";
2+
import { fileURLToPath } from "node:url";
3+
import { FlatCompat } from "@eslint/eslintrc";
4+
5+
const __filename = fileURLToPath(import.meta.url);
6+
const __dirname = dirname(__filename);
7+
8+
const compat = new FlatCompat({
9+
baseDirectory: __dirname,
10+
});
11+
12+
const eslintConfig = [
13+
...compat.extends("next/core-web-vitals", "next/typescript"),
14+
];
15+
16+
export default eslintConfig;

apps/login/next.config.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { NextConfig } from "next";
2+
3+
const nextConfig: NextConfig = {
4+
/* config options here */
5+
async headers() {
6+
return [
7+
{
8+
source: "/api/request",
9+
headers: [
10+
{
11+
key: "Access-Control-Allow-Origin",
12+
value: "*", // Set your origin
13+
},
14+
{
15+
key: "Access-Control-Allow-Methods",
16+
value: "GET, POST, PUT, DELETE, OPTIONS",
17+
},
18+
{
19+
key: "Access-Control-Allow-Headers",
20+
value: "Content-Type, Authorization",
21+
},
22+
],
23+
},
24+
];
25+
},
26+
};
27+
28+
export default nextConfig;

apps/login/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "thirdweb-login",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"preinstall": "npx only-allow pnpm",
7+
"dev": "next dev --turbopack",
8+
"build": "next build",
9+
"start": "next start",
10+
"format": "biome format ./src --write",
11+
"lint": "biome check ./src && knip && eslint ./src",
12+
"fix": "biome check ./src --fix && eslint ./src --fix",
13+
"typecheck": "tsc --noEmit",
14+
"knip": "knip"
15+
},
16+
"dependencies": {
17+
"next": "15.1.6",
18+
"react": "19.0.0",
19+
"react-dom": "19.0.0",
20+
"thirdweb": "workspace:*"
21+
},
22+
"devDependencies": {
23+
"@eslint/eslintrc": "^3",
24+
"@types/node": "22.10.10",
25+
"@types/react": "19.0.8",
26+
"@types/react-dom": "19.0.3",
27+
"eslint": "^9",
28+
"eslint-config-next": "15.1.6",
29+
"postcss": "8.5.1",
30+
"tailwindcss": "3.4.17",
31+
"typescript": "5.7.3"
32+
}
33+
}

apps/login/postcss.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** @type {import('postcss-load-config').Config} */
2+
const config = {
3+
plugins: {
4+
tailwindcss: {},
5+
},
6+
};
7+
8+
export default config;

apps/login/public/tw-login.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
(() => {
2+
const { targetId, clientId, baseUrl } = getSetup();
3+
4+
// the code to verify login was not tampered with
5+
let code = "";
6+
7+
const USER_ADDRESS_KEY = "tw.login:userAddress";
8+
const SESSION_KEY_ADDRESS_KEY = "tw.login:sessionKeyAddress";
9+
10+
function main() {
11+
// check if redirected first, this sets up the logged in state if it was from redirect
12+
const params = parseURLHash(new URL(window.location));
13+
console.log(params);
14+
// TECHNICALLY this should verify the code... but can't do that without backend of some sort
15+
if (params) {
16+
// reset the code
17+
code = "";
18+
// write the userAddress to local storage
19+
localStorage.setItem(USER_ADDRESS_KEY, params.userAddress);
20+
// write the sessionKeyAddress to local storage
21+
localStorage.setItem(SESSION_KEY_ADDRESS_KEY, params.sessionKeyAddress);
22+
// reset the URL hash
23+
window.location.hash = "";
24+
}
25+
26+
const userAddress = localStorage.getItem(USER_ADDRESS_KEY);
27+
const sessionKeyAddress = localStorage.getItem(SESSION_KEY_ADDRESS_KEY);
28+
29+
if (userAddress && sessionKeyAddress) {
30+
// handle logged in state
31+
handleIsLoggedIn();
32+
} else {
33+
// handle not logged in state
34+
handleNotLoggedIn();
35+
}
36+
}
37+
38+
function handleIsLoggedIn() {
39+
console.log("handleIsLoggedIn");
40+
41+
window.thirdweb = {
42+
isLoggedIn: true,
43+
getAddress: () => getAddress(),
44+
logout: () => {
45+
window.localStorage.removeItem(USER_ADDRESS_KEY);
46+
window.localStorage.removeItem(SESSION_KEY_ADDRESS_KEY);
47+
window.location.reload();
48+
},
49+
makeRequest: async () => {
50+
const res = await fetch(`${baseUrl}/api/request`, {
51+
method: "POST",
52+
body: JSON.stringify({
53+
userAddress: getAddress(),
54+
sessionKeyAddress: getSessionKeyAddress(),
55+
}),
56+
});
57+
const data = await res.json();
58+
console.log(data);
59+
},
60+
};
61+
}
62+
63+
function handleNotLoggedIn() {
64+
window.thirdweb = { login: onLogin, isLoggedIn: false };
65+
}
66+
67+
function onLogin() {
68+
code = window.crypto.getRandomValues(new Uint8Array(4)).join("");
69+
// redirect to the login page
70+
const redirect = new URL(baseUrl);
71+
redirect.searchParams.set("code", code);
72+
redirect.searchParams.set("clientId", clientId);
73+
redirect.searchParams.set("redirect", window.location.href);
74+
window.location.href = redirect.href;
75+
}
76+
77+
function getAddress() {
78+
return localStorage.getItem(USER_ADDRESS_KEY);
79+
}
80+
81+
function getSessionKeyAddress() {
82+
return localStorage.getItem(SESSION_KEY_ADDRESS_KEY);
83+
}
84+
85+
// utils
86+
87+
function getSetup() {
88+
const el = document.currentScript;
89+
if (!el) {
90+
throw new Error("Could not find script element");
91+
}
92+
const baseUrl = new URL(el.src).origin;
93+
const dataset = el.dataset;
94+
const targetId = dataset.target || "tw-login";
95+
const clientId = dataset.clientId;
96+
if (!clientId) {
97+
throw new Error("Missing client-id");
98+
}
99+
return { targetId, clientId, baseUrl };
100+
}
101+
102+
/**
103+
* @param {URL} url
104+
* @returns null | { [key: string]: string }
105+
*/
106+
function parseURLHash(url) {
107+
if (!url.hash) {
108+
return null;
109+
}
110+
try {
111+
return decodeHash(url.hash);
112+
} catch {
113+
// if this fails, invalid data -> return null
114+
return null;
115+
}
116+
}
117+
118+
/**
119+
* Decodes a URL hash string to extract the three keys.
120+
*
121+
* @param {string} hash - A string like "#eyJrZXkxIjoiVmFsdWU..."
122+
* @returns {{ userAddress: string, sessionKeyAddress: string, code: string }} An object with the three keys
123+
*/
124+
function decodeHash(hash) {
125+
// Remove the "#" prefix, if present.
126+
const base64Data = hash.startsWith("#") ? hash.slice(1) : hash;
127+
128+
// Decode the Base64 string, then parse the JSON.
129+
const jsonString = atob(base64Data);
130+
const data = JSON.parse(jsonString);
131+
132+
// data should have the shape { userAddress, sessionKeyAddress, code }.
133+
if (
134+
"userAddress" in data &&
135+
"sessionKeyAddress" in data &&
136+
"code" in data
137+
) {
138+
return data;
139+
}
140+
return null;
141+
}
142+
143+
main();
144+
})();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { type NextRequest, NextResponse } from "next/server";
2+
3+
export const POST = async (req: NextRequest) => {
4+
const body = await req.json();
5+
const userAddress = body.userAddress;
6+
const sessionKeyAddress = body.sessionKeyAddress;
7+
if (!userAddress || !sessionKeyAddress) {
8+
return NextResponse.json(
9+
{
10+
message: "Missing userAddress or sessionKeyAddress",
11+
},
12+
{ status: 400 },
13+
);
14+
}
15+
const url = `${ENGINE_URL}/contract/84532/0x638263e3eAa3917a53630e61B1fBa685308024fa/erc1155/claim-to`;
16+
17+
console.log("url", url);
18+
19+
const res = await fetch(url, {
20+
method: "POST",
21+
headers: {
22+
authorization: `Bearer ${ACCESS_TOKEN}`,
23+
"x-account-address": userAddress,
24+
"x-backend-wallet-address": sessionKeyAddress,
25+
"content-type": "application/json",
26+
},
27+
body: JSON.stringify({
28+
receiver: userAddress,
29+
tokenId: "0",
30+
quantity: "1",
31+
}),
32+
});
33+
const data = await res.json();
34+
35+
return NextResponse.json(data, { status: res.status });
36+
};

apps/login/src/app/favicon.ico

15 KB
Binary file not shown.

0 commit comments

Comments
 (0)