From 127ccc61b0e28acd7b9e51252fc8bb670bcb4ae2 Mon Sep 17 00:00:00 2001 From: Alberto Elias Date: Tue, 8 Oct 2024 07:59:20 +0200 Subject: [PATCH] New packages for Auth SDK (#797) * Cleans up how refresh tokens are received to avoid fetching multiple times * lint * removes unnecessary usecallbacks * Creates new Auth SDKs * Removes tests as we dont have them yet * changeset * Adds log to invalid jwt verification * lint --- .changeset/empty-bees-occur.md | 5 +++ .changeset/good-months-hear.md | 5 +++ .changeset/serious-camels-hang.md | 5 +++ .changeset/strange-knives-relax.md | 5 +++ .changeset/stupid-socks-scream.md | 5 +++ apps/payments/create-react-app/package.json | 27 ++--------- crossmint-sdk.code-workspace | 8 ++++ packages/client/auth-core/README.md | 3 ++ packages/client/auth-core/package.json | 6 +-- packages/client/auth/README.md | 2 + packages/client/auth/package.json | 25 +++++++++++ packages/client/auth/src/index.ts | 2 + packages/client/auth/src/utils/index.ts | 1 + packages/client/auth/src/utils/jwt.ts | 6 +++ packages/client/auth/tsconfig.json | 11 +++++ packages/client/ui/react-ui/package.json | 9 ++-- .../src/hooks/useRefreshToken.test.ts | 4 +- .../ui/react-ui/src/hooks/useRefreshToken.ts | 14 ++---- .../providers/CrossmintAuthProvider.test.tsx | 6 +-- .../src/providers/CrossmintAuthProvider.tsx | 5 ++- packages/common/auth/README.md | 2 + packages/common/auth/package.json | 24 ++++++++++ packages/common/auth/src/index.ts | 3 ++ .../auth/src/services/CrossmintAuthService.ts | 40 +++++++++++++++++ packages/common/auth/src/services/index.ts | 2 + packages/common/auth/src/services/logger.ts | 5 +++ .../common/auth/src/types/authmaterial.ts | 10 +++++ packages/common/auth/src/types/index.ts | 2 + packages/common/auth/src/types/user.ts | 16 +++++++ packages/common/auth/src/utils/constants.ts | 1 + packages/common/auth/src/utils/index.ts | 1 + packages/common/auth/tsconfig.json | 11 +++++ packages/server/README.md | 2 - packages/server/auth/README.md | 3 ++ packages/server/auth/package.json | 29 ++++++++++++ packages/server/auth/src/index.ts | 1 + packages/server/auth/src/utils/index.ts | 1 + packages/server/auth/src/utils/jwt.ts | 45 +++++++++++++++++++ .../auth/src/utils/tokenAuth/publicKey.ts | 28 ++++++++++++ packages/server/auth/tsconfig.json | 12 +++++ pnpm-lock.yaml | 38 +++++++++++++++- 41 files changed, 374 insertions(+), 56 deletions(-) create mode 100644 .changeset/empty-bees-occur.md create mode 100644 .changeset/good-months-hear.md create mode 100644 .changeset/serious-camels-hang.md create mode 100644 .changeset/strange-knives-relax.md create mode 100644 .changeset/stupid-socks-scream.md create mode 100644 packages/client/auth-core/README.md create mode 100644 packages/client/auth/README.md create mode 100644 packages/client/auth/package.json create mode 100644 packages/client/auth/src/index.ts create mode 100644 packages/client/auth/src/utils/index.ts create mode 100644 packages/client/auth/src/utils/jwt.ts create mode 100644 packages/client/auth/tsconfig.json create mode 100644 packages/common/auth/README.md create mode 100644 packages/common/auth/package.json create mode 100644 packages/common/auth/src/index.ts create mode 100644 packages/common/auth/src/services/CrossmintAuthService.ts create mode 100644 packages/common/auth/src/services/index.ts create mode 100644 packages/common/auth/src/services/logger.ts create mode 100644 packages/common/auth/src/types/authmaterial.ts create mode 100644 packages/common/auth/src/types/index.ts create mode 100644 packages/common/auth/src/types/user.ts create mode 100644 packages/common/auth/src/utils/constants.ts create mode 100644 packages/common/auth/src/utils/index.ts create mode 100644 packages/common/auth/tsconfig.json create mode 100644 packages/server/auth/README.md create mode 100644 packages/server/auth/package.json create mode 100644 packages/server/auth/src/index.ts create mode 100644 packages/server/auth/src/utils/index.ts create mode 100644 packages/server/auth/src/utils/jwt.ts create mode 100644 packages/server/auth/src/utils/tokenAuth/publicKey.ts create mode 100644 packages/server/auth/tsconfig.json diff --git a/.changeset/empty-bees-occur.md b/.changeset/empty-bees-occur.md new file mode 100644 index 000000000..1c6195d5f --- /dev/null +++ b/.changeset/empty-bees-occur.md @@ -0,0 +1,5 @@ +--- +"@crossmint/server-sdk-auth": minor +--- + +Initial release diff --git a/.changeset/good-months-hear.md b/.changeset/good-months-hear.md new file mode 100644 index 000000000..8987fa403 --- /dev/null +++ b/.changeset/good-months-hear.md @@ -0,0 +1,5 @@ +--- +"@crossmint/client-sdk-auth": minor +--- + +Initial release diff --git a/.changeset/serious-camels-hang.md b/.changeset/serious-camels-hang.md new file mode 100644 index 000000000..81271b28a --- /dev/null +++ b/.changeset/serious-camels-hang.md @@ -0,0 +1,5 @@ +--- +"@crossmint/client-sdk-react-ui": patch +--- + +Moves from auth core to new auth sdk diff --git a/.changeset/strange-knives-relax.md b/.changeset/strange-knives-relax.md new file mode 100644 index 000000000..f6ea19c84 --- /dev/null +++ b/.changeset/strange-knives-relax.md @@ -0,0 +1,5 @@ +--- +"@crossmint/client-sdk-auth-core": patch +--- + +Deprecated in favour of @crossmint/common-sdk-auth diff --git a/.changeset/stupid-socks-scream.md b/.changeset/stupid-socks-scream.md new file mode 100644 index 000000000..52a92a1d7 --- /dev/null +++ b/.changeset/stupid-socks-scream.md @@ -0,0 +1,5 @@ +--- +"@crossmint/common-sdk-auth": minor +--- + +Initial release diff --git a/apps/payments/create-react-app/package.json b/apps/payments/create-react-app/package.json index 695757526..95dbbb5a5 100644 --- a/apps/payments/create-react-app/package.json +++ b/apps/payments/create-react-app/package.json @@ -5,37 +5,18 @@ "repository": "https://github.com/Crossmint/crossmint-sdk", "license": "Apache-2.0", "author": "Paella Labs Inc", - "files": [ - "public", - "src", - ".env", - "LICENSE", - "package.json", - "README.md", - "tsconfig.json", - "yarn.lock" - ], + "files": ["public", "src", ".env", "LICENSE", "package.json", "README.md", "tsconfig.json", "yarn.lock"], "scripts": { "build": "react-scripts build", "eject": "react-scripts eject", "start": "react-scripts start" }, "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "production": [">0.2%", "not dead", "not op_mini all"], + "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] }, "eslintConfig": { - "extends": [ - "react-app" - ] + "extends": ["react-app"] }, "dependencies": { "@crossmint/client-sdk-react-ui": "workspace:*", diff --git a/crossmint-sdk.code-workspace b/crossmint-sdk.code-workspace index 00d20bb30..28cc72931 100644 --- a/crossmint-sdk.code-workspace +++ b/crossmint-sdk.code-workspace @@ -20,10 +20,18 @@ "name": "🧬 @crossmint/common-sdk-base", "path": "packages/common/base" }, + { + "name": "🧬 @crossmint/common-sdk-auth", + "path": "packages/common/auth" + }, { "name": "🧬 @crossmint/client-sdk-base", "path": "packages/client/base" }, + { + "name": "⚛️ @crossmint/client-sdk-auth", + "path": "packages/client/auth" + }, { "name": "⚛️ @crossmint/client-sdk-react-ui", "path": "packages/client/ui/react-ui" diff --git a/packages/client/auth-core/README.md b/packages/client/auth-core/README.md new file mode 100644 index 000000000..824849deb --- /dev/null +++ b/packages/client/auth-core/README.md @@ -0,0 +1,3 @@ +# @crossmint/client-sdk-auth-core + +DEPRECATED: This package is deprecated and has been replaced by @crossmint/common-sdk-auth, @crossmint/client-sdk-auth and @crossmint/server-sdk-auth. \ No newline at end of file diff --git a/packages/client/auth-core/package.json b/packages/client/auth-core/package.json index 5410a7bf9..44ff74623 100644 --- a/packages/client/auth-core/package.json +++ b/packages/client/auth-core/package.json @@ -23,11 +23,7 @@ "require": "./dist/client/index.cjs" } }, - "files": [ - "dist", - "src", - "LICENSE" - ], + "files": ["dist", "src", "LICENSE"], "scripts": { "build": "tsup server/index.ts client/index.ts --clean --format esm,cjs --outDir ./dist --minify --dts --sourcemap", "dev": "tsup server/index.ts client/index.ts --clean --format esm,cjs --outDir ./dist --dts --sourcemap --watch", diff --git a/packages/client/auth/README.md b/packages/client/auth/README.md new file mode 100644 index 000000000..8a16bf443 --- /dev/null +++ b/packages/client/auth/README.md @@ -0,0 +1,2 @@ +# @crossmint/client-sdk-auth + diff --git a/packages/client/auth/package.json b/packages/client/auth/package.json new file mode 100644 index 000000000..3c0a92e62 --- /dev/null +++ b/packages/client/auth/package.json @@ -0,0 +1,25 @@ +{ + "name": "@crossmint/client-sdk-auth", + "version": "0.1.0", + "repository": "https://github.com/Crossmint/crossmint-sdk", + "license": "Apache-2.0", + "author": "Paella Labs Inc", + "sideEffects": false, + "type": "module", + "exports": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": ["dist", "src", "LICENSE"], + "scripts": { + "build": "tsup src/index.ts --clean --format esm,cjs --outDir ./dist --minify --dts --sourcemap", + "dev": "tsup src/index.ts --clean --format esm,cjs --outDir ./dist --dts --sourcemap --watch" + }, + "dependencies": { + "@crossmint/common-sdk-auth": "workspace:*", + "jwt-decode": "4.0.0" + } +} diff --git a/packages/client/auth/src/index.ts b/packages/client/auth/src/index.ts new file mode 100644 index 000000000..8a43759fc --- /dev/null +++ b/packages/client/auth/src/index.ts @@ -0,0 +1,2 @@ +export { CrossmintAuthService } from "@crossmint/common-sdk-auth"; +export * from "./utils"; diff --git a/packages/client/auth/src/utils/index.ts b/packages/client/auth/src/utils/index.ts new file mode 100644 index 000000000..2d17df569 --- /dev/null +++ b/packages/client/auth/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./jwt"; diff --git a/packages/client/auth/src/utils/jwt.ts b/packages/client/auth/src/utils/jwt.ts new file mode 100644 index 000000000..9f5755a22 --- /dev/null +++ b/packages/client/auth/src/utils/jwt.ts @@ -0,0 +1,6 @@ +import { jwtDecode } from "jwt-decode"; + +export function getJWTExpiration(token: string) { + const decoded = jwtDecode(token); + return decoded.exp; +} diff --git a/packages/client/auth/tsconfig.json b/packages/client/auth/tsconfig.json new file mode 100644 index 000000000..1927574ca --- /dev/null +++ b/packages/client/auth/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"] +} diff --git a/packages/client/ui/react-ui/package.json b/packages/client/ui/react-ui/package.json index 76b808ff5..b538f979b 100644 --- a/packages/client/ui/react-ui/package.json +++ b/packages/client/ui/react-ui/package.json @@ -13,22 +13,19 @@ "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", - "files": [ - "dist", - "src", - "LICENSE" - ], + "files": ["dist", "src", "LICENSE"], "scripts": { "build": "tsup src/index.ts --clean --external react,react-dom --format esm,cjs --outDir ./dist --minify --dts --sourcemap", "dev": "tsup src/index.ts --clean --external react,react-dom --format esm,cjs --outDir ./dist --dts --sourcemap --watch", "test": "vitest run" }, "dependencies": { - "@crossmint/client-sdk-auth-core": "workspace:*", + "@crossmint/client-sdk-auth": "workspace:*", "@crossmint/client-sdk-base": "workspace:*", "@crossmint/client-sdk-smart-wallet": "workspace:*", "@crossmint/client-sdk-window": "workspace:*", "@crossmint/common-sdk-base": "workspace:*", + "@crossmint/common-sdk-auth": "workspace:*", "@ethersproject/transactions": "5.7.0", "@headlessui/react": "2.1.5", "@solana/web3.js": "1.95.1", diff --git a/packages/client/ui/react-ui/src/hooks/useRefreshToken.test.ts b/packages/client/ui/react-ui/src/hooks/useRefreshToken.test.ts index ee10657cb..008cbd810 100644 --- a/packages/client/ui/react-ui/src/hooks/useRefreshToken.test.ts +++ b/packages/client/ui/react-ui/src/hooks/useRefreshToken.test.ts @@ -1,13 +1,13 @@ import { act, renderHook } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { type CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth-core/client"; +import { type CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth"; import { queueTask } from "@crossmint/client-sdk-base"; import * as authCookies from "../utils/authCookies"; import { type AuthMaterial, useRefreshToken } from "./useRefreshToken"; -vi.mock("@crossmint/client-sdk-auth-core", () => ({ +vi.mock("@crossmint/client-sdk-auth", () => ({ CrossmintAuthService: vi.fn(), getJWTExpiration: vi.fn(), })); diff --git a/packages/client/ui/react-ui/src/hooks/useRefreshToken.ts b/packages/client/ui/react-ui/src/hooks/useRefreshToken.ts index 1c8f62fae..301313845 100644 --- a/packages/client/ui/react-ui/src/hooks/useRefreshToken.ts +++ b/packages/client/ui/react-ui/src/hooks/useRefreshToken.ts @@ -1,7 +1,8 @@ import { useCallback, useEffect, useRef } from "react"; -import type { CrossmintAuthService, SDKExternalUser } from "@crossmint/client-sdk-auth-core/client"; -import { getJWTExpiration } from "@crossmint/client-sdk-auth-core/client"; +import type { AuthMaterial } from "@crossmint/common-sdk-auth"; +import type { CrossmintAuthService } from "@crossmint/client-sdk-auth"; +import { getJWTExpiration } from "@crossmint/client-sdk-auth"; import { queueTask, type CancellableTask } from "@crossmint/client-sdk-base"; import { REFRESH_TOKEN_PREFIX, getCookie } from "../utils/authCookies"; @@ -9,15 +10,6 @@ import { REFRESH_TOKEN_PREFIX, getCookie } from "../utils/authCookies"; // 2 minutes before jwt expiration const TIME_BEFORE_EXPIRING_JWT_IN_SECONDS = 120; -export type AuthMaterial = { - jwtToken: string; - refreshToken: { - secret: string; - expiresAt: string; - }; - user: SDKExternalUser; -}; - type UseAuthTokenRefreshProps = { crossmintAuthService: CrossmintAuthService; setAuthMaterial: (authMaterial: AuthMaterial) => void; diff --git a/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.test.tsx b/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.test.tsx index 4026904e3..e135c3331 100644 --- a/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.test.tsx +++ b/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.test.tsx @@ -4,7 +4,7 @@ import { type ReactNode, act } from "react"; import { beforeEach, describe, expect, vi } from "vitest"; import { mock } from "vitest-mock-extended"; -import { CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth-core/client"; +import { CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth"; import { type EVMSmartWallet, SmartWalletSDK } from "@crossmint/client-sdk-smart-wallet"; import { createCrossmint } from "@crossmint/common-sdk-base"; @@ -32,8 +32,8 @@ vi.mock("@crossmint/common-sdk-base", async () => { }; }); -vi.mock("@crossmint/client-sdk-auth-core/client", async () => { - const actual = await vi.importActual("@crossmint/client-sdk-auth-core/client"); +vi.mock("@crossmint/client-sdk-auth", async () => { + const actual = await vi.importActual("@crossmint/client-sdk-auth"); return { ...actual, getJWTExpiration: vi.fn(), diff --git a/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.tsx b/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.tsx index 29d2c1bcb..41dd8e5c6 100644 --- a/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.tsx +++ b/packages/client/ui/react-ui/src/providers/CrossmintAuthProvider.tsx @@ -2,13 +2,14 @@ import { REFRESH_TOKEN_PREFIX, SESSION_PREFIX, deleteCookie, getCookie, setCooki import { type ReactNode, createContext, useEffect, useState } from "react"; import { createPortal } from "react-dom"; -import { CrossmintAuthService, type SDKExternalUser } from "@crossmint/client-sdk-auth-core/client"; +import { CrossmintAuthService } from "@crossmint/client-sdk-auth"; import type { EVMSmartWalletChain } from "@crossmint/client-sdk-smart-wallet"; import { type UIConfig, validateApiKeyAndGetCrossmintBaseUrl } from "@crossmint/common-sdk-base"; import AuthModal from "../components/auth/AuthModal"; -import { type AuthMaterial, useCrossmint, useRefreshToken, useWallet } from "../hooks"; +import { useCrossmint, useRefreshToken, useWallet } from "../hooks"; import { CrossmintWalletProvider } from "./CrossmintWalletProvider"; +import type { AuthMaterial, SDKExternalUser } from "@crossmint/common-sdk-auth"; export type CrossmintAuthWalletConfig = { defaultChain: EVMSmartWalletChain; diff --git a/packages/common/auth/README.md b/packages/common/auth/README.md new file mode 100644 index 000000000..2ca17ad22 --- /dev/null +++ b/packages/common/auth/README.md @@ -0,0 +1,2 @@ +# @crossmint/common-sdk-auth + diff --git a/packages/common/auth/package.json b/packages/common/auth/package.json new file mode 100644 index 000000000..e2dd2a11a --- /dev/null +++ b/packages/common/auth/package.json @@ -0,0 +1,24 @@ +{ + "name": "@crossmint/common-sdk-auth", + "version": "0.1.0", + "repository": "https://github.com/Crossmint/crossmint-sdk", + "license": "Apache-2.0", + "author": "Paella Labs Inc", + "sideEffects": false, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "files": ["dist", "src", "LICENSE"], + "scripts": { + "build": "tsup src/index.ts --clean --format esm,cjs --outDir ./dist --minify --dts --sourcemap", + "dev": "tsup src/index.ts --clean --format esm,cjs --outDir ./dist --dts --sourcemap --watch" + }, + "dependencies": { + "@crossmint/client-sdk-base": "workspace:*" + } +} diff --git a/packages/common/auth/src/index.ts b/packages/common/auth/src/index.ts new file mode 100644 index 000000000..d301686d3 --- /dev/null +++ b/packages/common/auth/src/index.ts @@ -0,0 +1,3 @@ +export * from "./services"; +export * from "./utils"; +export * from "./types"; diff --git a/packages/common/auth/src/services/CrossmintAuthService.ts b/packages/common/auth/src/services/CrossmintAuthService.ts new file mode 100644 index 000000000..0040d289f --- /dev/null +++ b/packages/common/auth/src/services/CrossmintAuthService.ts @@ -0,0 +1,40 @@ +import { APIErrorService, BaseCrossmintService } from "@crossmint/client-sdk-base"; + +import { authLogger } from "./logger"; +import type { AuthMaterial } from "@/types"; + +export class CrossmintAuthService extends BaseCrossmintService { + protected apiErrorService = new APIErrorService({}); + protected logger = authLogger; + + public getJWKSUri() { + return `${this.crossmintBaseUrl}/.well-known/jwks.json`; + } + + async refreshAuthMaterial(refreshToken: string): Promise { + const result = await this.fetchCrossmintAPI( + "2024-09-26/session/sdk/auth/refresh", + { method: "POST", body: JSON.stringify({ refresh: refreshToken }) }, + "Error fetching new refresh and access tokans." + ); + + return { + jwtToken: result.jwt, + refreshToken: result.refresh, + user: result.user, + }; + } + + async getUserFromServer(externalUserId: string) { + const result = await this.fetchCrossmintAPI( + `sdk/auth/user/${externalUserId}`, + { method: "GET" }, + "Error fetching user." + ); + + return result.user; + } + async getUserFromClient(jwt: string) { + return await this.fetchCrossmintAPI("sdk/auth/user", { method: "GET" }, "Error fetching user.", jwt); + } +} diff --git a/packages/common/auth/src/services/index.ts b/packages/common/auth/src/services/index.ts new file mode 100644 index 000000000..025e03dee --- /dev/null +++ b/packages/common/auth/src/services/index.ts @@ -0,0 +1,2 @@ +export * from "./CrossmintAuthService"; +export * from "./logger"; diff --git a/packages/common/auth/src/services/logger.ts b/packages/common/auth/src/services/logger.ts new file mode 100644 index 000000000..03084098f --- /dev/null +++ b/packages/common/auth/src/services/logger.ts @@ -0,0 +1,5 @@ +import { SDKLogger } from "@crossmint/client-sdk-base"; + +import { AUTH_SERVICE } from "../utils/constants"; + +export const authLogger = new SDKLogger(AUTH_SERVICE); diff --git a/packages/common/auth/src/types/authmaterial.ts b/packages/common/auth/src/types/authmaterial.ts new file mode 100644 index 000000000..56d7ae7e6 --- /dev/null +++ b/packages/common/auth/src/types/authmaterial.ts @@ -0,0 +1,10 @@ +import type { SDKExternalUser } from "./user"; + +export type AuthMaterial = { + jwtToken: string; + refreshToken: { + secret: string; + expiresAt: string; + }; + user: SDKExternalUser; +}; diff --git a/packages/common/auth/src/types/index.ts b/packages/common/auth/src/types/index.ts new file mode 100644 index 000000000..10f36d45d --- /dev/null +++ b/packages/common/auth/src/types/index.ts @@ -0,0 +1,2 @@ +export * from "./user"; +export * from "./authmaterial"; diff --git a/packages/common/auth/src/types/user.ts b/packages/common/auth/src/types/user.ts new file mode 100644 index 000000000..2f6b03de4 --- /dev/null +++ b/packages/common/auth/src/types/user.ts @@ -0,0 +1,16 @@ +export type FarcasterMetadata = { + fid: string; + username: string; + bio: string; + displayName: string; + pfpUrl: string; + custody: string; + verifications: string[]; +}; + +export type SDKExternalUser = { + id: string; + email?: string; + phoneNumber?: string; + farcaster?: FarcasterMetadata; +}; diff --git a/packages/common/auth/src/utils/constants.ts b/packages/common/auth/src/utils/constants.ts new file mode 100644 index 000000000..0e726bae2 --- /dev/null +++ b/packages/common/auth/src/utils/constants.ts @@ -0,0 +1 @@ +export const AUTH_SERVICE = "AUTH_SDK"; diff --git a/packages/common/auth/src/utils/index.ts b/packages/common/auth/src/utils/index.ts new file mode 100644 index 000000000..b04bfcf75 --- /dev/null +++ b/packages/common/auth/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./constants"; diff --git a/packages/common/auth/tsconfig.json b/packages/common/auth/tsconfig.json new file mode 100644 index 000000000..1927574ca --- /dev/null +++ b/packages/common/auth/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"] +} diff --git a/packages/server/README.md b/packages/server/README.md index 1c6e401c2..797eb3972 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -1,3 +1 @@ # @crossmint/server-sdk - -TODO diff --git a/packages/server/auth/README.md b/packages/server/auth/README.md new file mode 100644 index 000000000..824849deb --- /dev/null +++ b/packages/server/auth/README.md @@ -0,0 +1,3 @@ +# @crossmint/client-sdk-auth-core + +DEPRECATED: This package is deprecated and has been replaced by @crossmint/common-sdk-auth, @crossmint/client-sdk-auth and @crossmint/server-sdk-auth. \ No newline at end of file diff --git a/packages/server/auth/package.json b/packages/server/auth/package.json new file mode 100644 index 000000000..680d4313d --- /dev/null +++ b/packages/server/auth/package.json @@ -0,0 +1,29 @@ +{ + "name": "@crossmint/server-sdk-auth", + "version": "0.1.0", + "repository": "https://github.com/Crossmint/crossmint-sdk", + "license": "Apache-2.0", + "author": "Paella Labs Inc", + "sideEffects": false, + "type": "module", + "exports": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": ["dist", "src", "LICENSE"], + "scripts": { + "build": "tsup src/index.ts --clean --format esm,cjs --outDir ./dist --minify --dts --sourcemap", + "dev": "tsup src/index.ts --clean --format esm,cjs --outDir ./dist --dts --sourcemap --watch" + }, + "dependencies": { + "@crossmint/common-sdk-auth": "workspace:*", + "jsonwebtoken": "9.0.2", + "jwks-rsa": "3.1.0" + }, + "devDependencies": { + "@types/jsonwebtoken": "9.0.6" + } +} diff --git a/packages/server/auth/src/index.ts b/packages/server/auth/src/index.ts new file mode 100644 index 000000000..178cd64f8 --- /dev/null +++ b/packages/server/auth/src/index.ts @@ -0,0 +1 @@ +export * from "./utils"; diff --git a/packages/server/auth/src/utils/index.ts b/packages/server/auth/src/utils/index.ts new file mode 100644 index 000000000..2d17df569 --- /dev/null +++ b/packages/server/auth/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./jwt"; diff --git a/packages/server/auth/src/utils/jwt.ts b/packages/server/auth/src/utils/jwt.ts new file mode 100644 index 000000000..bdf3bd0ae --- /dev/null +++ b/packages/server/auth/src/utils/jwt.ts @@ -0,0 +1,45 @@ +import { CrossmintAuthService } from "@crossmint/common-sdk-auth"; +import { JsonWebTokenError, TokenExpiredError, verify } from "jsonwebtoken"; + +import { getPublicKey } from "./tokenAuth/publicKey"; + +export async function verifyCrossmintSessionToken(apiKey: string, token: string) { + const crossmintService = new CrossmintAuthService(apiKey); + try { + return await verifyJWTWithPublicKey(crossmintService, token); + } catch (error) { + console.log("Error verifying JWT", error); + throw new Error("Invalid token"); + } +} + +function verifyJWT(signingKey: string, token: string) { + try { + const verifiedToken = verify(token, signingKey); + + if (verifiedToken == null || typeof verifiedToken === "string") { + throw new Error("Invalid token"); + } + return verifiedToken; + } catch (err: unknown) { + if (err != null && err instanceof TokenExpiredError) { + throw new Error(`JWT provided expired at timestamp ${err.expiredAt.toISOString()}`); + } + + if ( + err != null && + err instanceof JsonWebTokenError && + (err.message.includes("invalid signature") || err.message.includes("invalid algorithm")) + ) { + throw new Error(err.message); + } + + throw new Error("Invalid token"); + } +} + +async function verifyJWTWithPublicKey(crossmintService: CrossmintAuthService, token: string) { + const publicKey = await getPublicKey(token, crossmintService.getJWKSUri()); + + return verifyJWT(publicKey, token); +} diff --git a/packages/server/auth/src/utils/tokenAuth/publicKey.ts b/packages/server/auth/src/utils/tokenAuth/publicKey.ts new file mode 100644 index 000000000..fe3eb46ce --- /dev/null +++ b/packages/server/auth/src/utils/tokenAuth/publicKey.ts @@ -0,0 +1,28 @@ +import { decode } from "jsonwebtoken"; +import { JwksClient } from "jwks-rsa"; + +export async function getPublicKey(token: string, jwksUri: string) { + const decoded = decode(token, { complete: true }); + if (decoded?.header == null) { + throw new Error("Invalid Token: Header Formatted Incorrectly"); + } + + const signingKey = await getSigningKey(jwksUri, decoded.header.kid); + const publicKey = signingKey.getPublicKey(); + + return publicKey; +} + +async function getSigningKey(jwksUri: string, kid?: string) { + const client = new JwksClient({ + jwksUri, + cache: true, + }); + + try { + const signingKey = await client.getSigningKey(kid); + return signingKey; + } catch (error) { + throw new Error("Unable to retrieve signing key"); + } +} diff --git a/packages/server/auth/tsconfig.json b/packages/server/auth/tsconfig.json new file mode 100644 index 000000000..84657ccd0 --- /dev/null +++ b/packages/server/auth/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + "module": "commonjs" + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a71161af..c57e9586f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -386,6 +386,15 @@ importers: specifier: ^0.11.0 version: 0.11.3 + packages/client/auth: + dependencies: + '@crossmint/common-sdk-auth': + specifier: workspace:* + version: link:../../common/auth + jwt-decode: + specifier: 4.0.0 + version: 4.0.0 + packages/client/auth-core: dependencies: '@crossmint/client-sdk-base': @@ -432,9 +441,9 @@ importers: packages/client/ui/react-ui: dependencies: - '@crossmint/client-sdk-auth-core': + '@crossmint/client-sdk-auth': specifier: workspace:* - version: link:../../auth-core + version: link:../../auth '@crossmint/client-sdk-base': specifier: workspace:* version: link:../../base @@ -444,6 +453,9 @@ importers: '@crossmint/client-sdk-window': specifier: workspace:* version: link:../../window + '@crossmint/common-sdk-auth': + specifier: workspace:* + version: link:../../../common/auth '@crossmint/common-sdk-base': specifier: workspace:* version: link:../../../common/base @@ -754,6 +766,12 @@ importers: specifier: 3.22.4 version: 3.22.4 + packages/common/auth: + dependencies: + '@crossmint/client-sdk-base': + specifier: workspace:* + version: link:../../client/base + packages/common/base: dependencies: bs58: @@ -767,6 +785,22 @@ importers: specifier: ^4.0.4 version: 4.0.4 + packages/server/auth: + dependencies: + '@crossmint/common-sdk-auth': + specifier: workspace:* + version: link:../../common/auth + jsonwebtoken: + specifier: 9.0.2 + version: 9.0.2 + jwks-rsa: + specifier: 3.1.0 + version: 3.1.0 + devDependencies: + '@types/jsonwebtoken': + specifier: 9.0.6 + version: 9.0.6 + packages: '@aashutoshrathi/word-wrap@1.2.6':