diff --git a/.env.example b/.env.example index 8c19a39..366103d 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -PRIVATE_RESEND_KEY= \ No newline at end of file +PRIVATE_RESEND_KEY= +PRIVATE_SERVER_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 610aef7..c20a187 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ Thumbs.db vite.config.js.timestamp-* vite.config.ts.timestamp-* + +server_key.json \ No newline at end of file diff --git a/README.md b/README.md index 4c77baf..214bcdf 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ # DID UCAN + +## Generate Server Keys + +```bash +pnpm generate-server-keys +``` diff --git a/docs/ucan-instruction.md b/docs/ucan-instruction.md new file mode 100644 index 0000000..79b732d --- /dev/null +++ b/docs/ucan-instruction.md @@ -0,0 +1,94 @@ +# UCAN Tutorial + +## Prerequisites + +Two repositories: + +- [did-ucan](https://github.com/ic3software/did-ucan) +- [ucan-authz-service](https://github.com/ic3software/ucan-authz-service) + +## Step 1: Clone Both Repositories + +```bash +git clone https://github.com/ic3software/did-ucan.git +git clone https://github.com/ic3software/ucan-authz-service.git +``` + +## Step 2: Install Dependencies + +```bash +cd did-ucan +pnpm install +``` + +```bash +cd ucan-authz-service +npm install +``` + +## Step 3: Generate Server Keypairs + +You need to generate two sets of keypairs for the `did-ucan` and `ucan-authz-service` repositories. This means you should run the following command **twice**, resulting in two private keys and two DIDs. In the following steps, I will refer to them as `private_key1` and `did_key1`, and `private_key2` and `did_key2`. + +```bash +cd did-ucan +pnpm generate-server-keys +``` + +## Step 4: Setup .env Files + +You need to setup the .env files for the `did-ucan` and `ucan-authz-service` repositories. + +```bash +cp .env.example .env +``` + +For the `did-ucan` repository, you need to setup the following variables: + +```bash +PRIVATE_SERVER_KEY = private_key1 +``` + +We set up `did_key1` here to verify that the top-level UCAN i ssuer is indeed our `did-ucan` service. This ensures the trustworthiness and integrity of the entire authorization flow. + +For the `ucan-authz-service` repository, you need to setup the following variables: + +```bash +PRIVATE_ROOT_ISSUER_DID_KEY = did_key1 +PRIVATE_KEY = private_key2 +``` + +## Step 5: Start the did-ucan Service + +1. Start the did-ucan service + + ```bash + cd did-ucan + pnpm dev + ``` + +2. Open browser → click the `Generate UCAN` button +3. Click `Generate UCAN Token` Button +4. Click `Copy UCAN Token` Button + +## Step 6: Start the ucan-authz-service + +1. Start the ucan-authz-service + + ```bash + cd ucan-authz-service + pnpm dev + ``` + +2. Open browser → Click `Go To Delegate Page` Button +3. Paste UCAN token → Click `Parse Token` Button +4. Select capabilities to delegate +5. Click `Generate New Token` Button +6. Click `Copy Token` Button +7. Click `Go To Home` Button + +## Step 7: Test the Token in the ucan-authz-service + +1. Paste the token on the homepage +2. Use the three buttons to test permissions + - It will display granted or denied based on what you selected earlier diff --git a/drizzle/0000_daily_impossible_man.sql b/drizzle/0000_daily_impossible_man.sql new file mode 100644 index 0000000..9823423 --- /dev/null +++ b/drizzle/0000_daily_impossible_man.sql @@ -0,0 +1,39 @@ +CREATE TABLE `emails` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` integer NOT NULL, + `email` text NOT NULL, + `created_at` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `emails_email_unique` ON `emails` (`email`);--> statement-breakpoint +CREATE TABLE `login_tokens` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` integer NOT NULL, + `token` text NOT NULL, + `expires_at` integer NOT NULL, + `created_at` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `login_tokens_token_unique` ON `login_tokens` (`token`);--> statement-breakpoint +CREATE TABLE `public_keys` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` integer NOT NULL, + `public_key` text NOT NULL, + `created_at` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `public_keys_public_key_unique` ON `public_keys` (`public_key`);--> statement-breakpoint +CREATE TABLE `users` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text NOT NULL, + `normalized_name` text NOT NULL, + `email_reset` integer DEFAULT false NOT NULL, + `created_at` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + `updated_at` integer DEFAULT CURRENT_TIMESTAMP NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `users_name_unique` ON `users` (`name`);--> statement-breakpoint +CREATE UNIQUE INDEX `users_normalized_name_unique` ON `users` (`normalized_name`); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..073ccbd --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,278 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "8832268d-a5d8-418a-a912-6dab057d9e62", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "emails": { + "name": "emails", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "emails_email_unique": { + "name": "emails_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": { + "emails_user_id_users_id_fk": { + "name": "emails_user_id_users_id_fk", + "tableFrom": "emails", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "login_tokens": { + "name": "login_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "login_tokens_token_unique": { + "name": "login_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "login_tokens_user_id_users_id_fk": { + "name": "login_tokens_user_id_users_id_fk", + "tableFrom": "login_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "public_keys": { + "name": "public_keys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "public_keys_public_key_unique": { + "name": "public_keys_public_key_unique", + "columns": [ + "public_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "public_keys_user_id_users_id_fk": { + "name": "public_keys_user_id_users_id_fk", + "tableFrom": "public_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "normalized_name": { + "name": "normalized_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_reset": { + "name": "email_reset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "users_name_unique": { + "name": "users_name_unique", + "columns": [ + "name" + ], + "isUnique": true + }, + "users_normalized_name_unique": { + "name": "users_normalized_name_unique", + "columns": [ + "normalized_name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..98a1e05 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1744439650114, + "tag": "0000_daily_impossible_man", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 4d45165..ad53e85 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,16 @@ "test:unit": "vitest", "test": "npm run test:unit -- --run", "db:generate": "pnpm exec drizzle-kit generate", - "db:migrate": "pnpm exec wrangler d1 migrations apply did-auth-preview --local", - "db:migrate:preview": "pnpm exec wrangler d1 migrations apply did-auth-preview --remote", - "db:migrate:prod": "pnpm exec wrangler d1 migrations apply did-auth --remote --env production" + "db:migrate": "pnpm exec wrangler d1 migrations apply did-ucan-preview --local", + "db:migrate:preview": "pnpm exec wrangler d1 migrations apply did-ucan-preview --remote", + "db:migrate:prod": "pnpm exec wrangler d1 migrations apply did-ucan --remote --env production", + "generate-server-keys": "pnpm exec tsx scripts/generate-server-keys.ts" }, "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "@sveltejs/adapter-cloudflare": "^5.1.0", "@sveltejs/kit": "^2.20.2", + "@ucans/ucans": "^0.12.0", "drizzle-orm": "^0.40.1", "resend": "^4.2.0", "svelte": "^5.25.3", @@ -55,6 +57,7 @@ "svelte": "^5.0.0", "svelte-check": "^4.0.0", "tailwindcss": "^4.0.0", + "tsx": "^4.19.3", "typescript": "^5.0.0", "typescript-eslint": "^8.28.0", "vite": "^6.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a09896..cd4a6e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,13 @@ importers: version: 2.2.2 '@sveltejs/adapter-cloudflare': specifier: ^5.1.0 - version: 5.1.0(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(wrangler@4.6.0(@cloudflare/workers-types@4.20250327.0)) + version: 5.1.0(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(wrangler@4.6.0(@cloudflare/workers-types@4.20250327.0)) '@sveltejs/kit': specifier: ^2.20.2 - version: 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + version: 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) + '@ucans/ucans': + specifier: ^0.12.0 + version: 0.12.0 drizzle-orm: specifier: ^0.40.1 version: 0.40.1(@cloudflare/workers-types@4.20250327.0)(gel@2.0.1) @@ -44,16 +47,16 @@ importers: version: 9.23.0 '@sveltejs/vite-plugin-svelte': specifier: ^5.0.0 - version: 5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + version: 5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) '@tailwindcss/vite': specifier: ^4.0.17 - version: 4.0.17(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + version: 4.0.17(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 '@testing-library/svelte': specifier: ^5.2.4 - version: 5.2.7(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))(vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(yaml@2.7.0)) + version: 5.2.7(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) '@trivago/prettier-plugin-sort-imports': specifier: ^5.2.2 version: 5.2.2(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.25.3))(prettier@3.5.3)(svelte@5.25.3) @@ -96,6 +99,9 @@ importers: svelte-check: specifier: ^4.0.0 version: 4.1.5(svelte@5.25.3)(typescript@5.8.2) + tsx: + specifier: ^4.19.3 + version: 4.19.3 typescript: specifier: ^5.0.0 version: 5.8.2 @@ -104,10 +110,10 @@ importers: version: 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) vite: specifier: ^6.2.5 - version: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + version: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) vitest: specifier: ^3.0.9 - version: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(yaml@2.7.0) + version: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) wrangler: specifier: ^4.6.0 version: 4.6.0(@cloudflare/workers-types@4.20250327.0) @@ -1155,6 +1161,27 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@stablelib/binary@1.0.1': + resolution: {integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==} + + '@stablelib/ed25519@1.0.3': + resolution: {integrity: sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg==} + + '@stablelib/hash@1.0.1': + resolution: {integrity: sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg==} + + '@stablelib/int@1.0.1': + resolution: {integrity: sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==} + + '@stablelib/random@1.0.2': + resolution: {integrity: sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==} + + '@stablelib/sha512@1.0.1': + resolution: {integrity: sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw==} + + '@stablelib/wipe@1.0.1': + resolution: {integrity: sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==} + '@sveltejs/acorn-typescript@1.0.5': resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} peerDependencies: @@ -1370,6 +1397,18 @@ packages: resolution: {integrity: sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ucans/core@0.12.0': + resolution: {integrity: sha512-PWk9LDyuUdyGeucqenmG2BRo4bPUJDgxuYZidKD/OKTggyfFlcE/9UK1qpg1gJiS4HpGbm40gsvCmKgLUt0Smg==} + engines: {node: '>=15'} + + '@ucans/default-plugins@0.12.0': + resolution: {integrity: sha512-RJm0M2fFK1VBQh49YlJ9xfWFBzGPoKWAN787KfdDdhGCtavZqGDTgDXx7L7RnWdukLfIT4BZ5cVN/hiOkY5jyg==} + engines: {node: '>=15'} + + '@ucans/ucans@0.12.0': + resolution: {integrity: sha512-XWFCfpi595sv8Bx6KbZpdR+GPSIlrit3J8CNtbmn+wuvk5IqAORMdsLC+w51f4JPM7XgLZ4W0JxaggeHDRylaw==} + engines: {node: '>=15'} + '@vitest/expect@3.0.9': resolution: {integrity: sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==} @@ -1476,6 +1515,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} @@ -2378,6 +2421,9 @@ packages: multiformats@13.3.2: resolution: {integrity: sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==} + multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + mustache@4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true @@ -2400,6 +2446,9 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + one-webcrypto@1.0.3: + resolution: {integrity: sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==} + onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -2850,6 +2899,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.19.3: + resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2869,6 +2923,9 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + uint8arrays@3.0.0: + resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} + uint8arrays@5.1.0: resolution: {integrity: sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==} @@ -3758,21 +3815,48 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 + '@stablelib/binary@1.0.1': + dependencies: + '@stablelib/int': 1.0.1 + + '@stablelib/ed25519@1.0.3': + dependencies: + '@stablelib/random': 1.0.2 + '@stablelib/sha512': 1.0.1 + '@stablelib/wipe': 1.0.1 + + '@stablelib/hash@1.0.1': {} + + '@stablelib/int@1.0.1': {} + + '@stablelib/random@1.0.2': + dependencies: + '@stablelib/binary': 1.0.1 + '@stablelib/wipe': 1.0.1 + + '@stablelib/sha512@1.0.1': + dependencies: + '@stablelib/binary': 1.0.1 + '@stablelib/hash': 1.0.1 + '@stablelib/wipe': 1.0.1 + + '@stablelib/wipe@1.0.1': {} + '@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)': dependencies: acorn: 8.14.1 - '@sveltejs/adapter-cloudflare@5.1.0(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(wrangler@4.6.0(@cloudflare/workers-types@4.20250327.0))': + '@sveltejs/adapter-cloudflare@5.1.0(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(wrangler@4.6.0(@cloudflare/workers-types@4.20250327.0))': dependencies: '@cloudflare/workers-types': 4.20250327.0 - '@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + '@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) esbuild: 0.24.2 worktop: 0.8.0-next.18 wrangler: 4.6.0(@cloudflare/workers-types@4.20250327.0) - '@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))': + '@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 @@ -3785,27 +3869,27 @@ snapshots: set-cookie-parser: 2.7.1 sirv: 3.0.1 svelte: 5.25.3 - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) debug: 4.4.0 svelte: 5.25.3 - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))': + '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)))(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: 5.25.3 - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) - vitefu: 1.0.6(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) + vitefu: 1.0.6(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) transitivePeerDependencies: - supports-color @@ -3862,13 +3946,13 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.0.17 '@tailwindcss/oxide-win32-x64-msvc': 4.0.17 - '@tailwindcss/vite@4.0.17(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))': + '@tailwindcss/vite@4.0.17(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@tailwindcss/node': 4.0.17 '@tailwindcss/oxide': 4.0.17 lightningcss: 1.29.2 tailwindcss: 4.0.17 - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) '@testing-library/dom@10.4.0': dependencies: @@ -3891,13 +3975,13 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/svelte@5.2.7(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))(vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(yaml@2.7.0))': + '@testing-library/svelte@5.2.7(svelte@5.25.3)(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@testing-library/dom': 10.4.0 svelte: 5.25.3 optionalDependencies: - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) - vitest: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) + vitest: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) '@trivago/prettier-plugin-sort-imports@5.2.2(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.25.3))(prettier@3.5.3)(svelte@5.25.3)': dependencies: @@ -4005,6 +4089,23 @@ snapshots: '@typescript-eslint/types': 8.28.0 eslint-visitor-keys: 4.2.0 + '@ucans/core@0.12.0': + dependencies: + uint8arrays: 3.0.0 + + '@ucans/default-plugins@0.12.0': + dependencies: + '@stablelib/ed25519': 1.0.3 + '@ucans/core': 0.12.0 + big-integer: 1.6.52 + one-webcrypto: 1.0.3 + uint8arrays: 3.0.0 + + '@ucans/ucans@0.12.0': + dependencies: + '@ucans/core': 0.12.0 + '@ucans/default-plugins': 0.12.0 + '@vitest/expect@3.0.9': dependencies: '@vitest/spy': 3.0.9 @@ -4012,13 +4113,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.9(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))': + '@vitest/mocker@3.0.9(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.0.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) '@vitest/pretty-format@3.0.9': dependencies: @@ -4100,6 +4201,8 @@ snapshots: balanced-match@1.0.2: {} + big-integer@1.6.52: {} + blake3-wasm@2.1.5: {} brace-expansion@1.1.11: @@ -4999,6 +5102,8 @@ snapshots: multiformats@13.3.2: {} + multiformats@9.9.0: {} + mustache@4.2.0: {} nanoid@3.3.11: {} @@ -5013,6 +5118,8 @@ snapshots: ohash@2.0.11: {} + one-webcrypto@1.0.3: {} + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -5411,6 +5518,13 @@ snapshots: tslib@2.8.1: optional: true + tsx@4.19.3: + dependencies: + esbuild: 0.25.2 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5429,6 +5543,10 @@ snapshots: ufo@1.5.4: {} + uint8arrays@3.0.0: + dependencies: + multiformats: 9.9.0 + uint8arrays@5.1.0: dependencies: multiformats: 13.3.2 @@ -5453,13 +5571,13 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0): + vite-node@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - jiti @@ -5474,7 +5592,7 @@ snapshots: - tsx - yaml - vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0): + vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: esbuild: 0.25.2 postcss: 8.5.3 @@ -5484,16 +5602,17 @@ snapshots: fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.29.2 + tsx: 4.19.3 yaml: 2.7.0 - vitefu@1.0.6(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)): + vitefu@1.0.6(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)): optionalDependencies: - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) - vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(yaml@2.7.0): + vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: '@vitest/expect': 3.0.9 - '@vitest/mocker': 3.0.9(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + '@vitest/mocker': 3.0.9(vite@6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0)) '@vitest/pretty-format': 3.0.9 '@vitest/runner': 3.0.9 '@vitest/snapshot': 3.0.9 @@ -5509,8 +5628,8 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) - vite-node: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) + vite: 6.2.5(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) + vite-node: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.13.14 diff --git a/scripts/generate-server-keys.ts b/scripts/generate-server-keys.ts new file mode 100644 index 0000000..7a89d7e --- /dev/null +++ b/scripts/generate-server-keys.ts @@ -0,0 +1,17 @@ +import { EdKeypair } from '@ucans/ucans'; +import fs from 'fs/promises'; + +async function generateKey() { + const keypair = await EdKeypair.create({ + exportable: true + }); + const exported = await keypair.export(); + + const jsonString = JSON.stringify(exported); + + await fs.writeFile('./server_key.json', jsonString); + console.log('✅ Server key:', jsonString); + console.log('✅ DID:', keypair.did()); +} + +generateKey().catch(console.error); diff --git a/src/lib/api.ts b/src/lib/api.ts index 5b926cc..28f3583 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -23,6 +23,10 @@ async function fetchApi(method: string, endpoint: string, payload: ApiPayload = try { await loadKeyPair(); + if (!keypair) { + throw new Error('No keypair found'); + } + const xTimer = Math.floor(Date.now()); const signature = await signRequest(payload, keypair!.privateKey); const xTimerSignature = await signRequest(xTimer, keypair!.privateKey); @@ -67,3 +71,7 @@ export async function fetchUserEmailReset(method: string, payload: ApiPayload = export async function fetchEmailReset(method: string, payload: ApiPayload = {}) { return await fetchApi(method, '/api/emails/reset', payload); } + +export async function fetchUCAN(method: string, payload: ApiPayload = {}) { + return await fetchApi(method, '/api/ucans', payload); +} diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index 88f362d..c03bc98 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -92,13 +92,16 @@ export async function signRequest(payload: unknown, privateKey: CryptoKey): Prom } } +export const EDWARDS_DID_PREFIX = new Uint8Array([0xed, 0x01]); + /** * Exports the public key as a base58btc string. */ export async function exportPublicKey(publicKey: CryptoKey): Promise { try { const exported = await crypto.subtle.exportKey('raw', publicKey); - return uint8arrays.toString(new Uint8Array(exported), 'base58btc'); + const prefixed = uint8arrays.concat([EDWARDS_DID_PREFIX, new Uint8Array(exported)]); + return uint8arrays.toString(prefixed, 'base58btc'); } catch (error) { console.error('Error exporting public key:', error); throw new Error('Failed to export public key'); diff --git a/src/lib/server/db/crypto.server.ts b/src/lib/server/db/crypto.server.ts index 4a8d45f..35fff57 100644 --- a/src/lib/server/db/crypto.server.ts +++ b/src/lib/server/db/crypto.server.ts @@ -35,7 +35,8 @@ export async function verifySignature( } // Finally, verify the timer and payload signatures - const publicKeyBytes = uint8arrays.fromString(XPublicKey, 'base58btc'); + const publicKeyBytesWithPrefix = uint8arrays.fromString(XPublicKey, 'base58btc'); + const publicKeyBytes = publicKeyBytesWithPrefix.slice(2); const importedKey = await crypto.subtle.importKey('raw', publicKeyBytes, 'Ed25519', false, [ 'verify' ]); diff --git a/src/lib/ucan/capabilities/email.ts b/src/lib/ucan/capabilities/email.ts new file mode 100644 index 0000000..c0204a7 --- /dev/null +++ b/src/lib/ucan/capabilities/email.ts @@ -0,0 +1,8 @@ +import type { Capability } from '@ucans/ucans'; + +export function emailCapability(action: 'create' | 'read' | 'delete', did: string): Capability { + return { + with: { scheme: 'mailto', hierPart: did }, + can: { namespace: 'email', segments: [action] } + }; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 74496c5..d5c7621 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -494,5 +494,12 @@ > {/if} +
+ +
diff --git a/src/routes/api/ucans/+server.ts b/src/routes/api/ucans/+server.ts new file mode 100644 index 0000000..0cf44c5 --- /dev/null +++ b/src/routes/api/ucans/+server.ts @@ -0,0 +1,64 @@ +import * as ucans from '@ucans/ucans'; +import { PRIVATE_SERVER_KEY } from '$env/static/private'; +import { authenticateRequest } from '$lib/server/auth'; +import { getUserIdByPublicKey } from '$lib/server/models/publicKey'; +import { emailCapability } from '$lib/ucan/capabilities/email'; +import type { D1Database } from '@cloudflare/workers-types'; +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from '@sveltejs/kit'; +import type { EdKeypair } from '@ucans/ucans'; + +let keypair: EdKeypair; + +export const POST: RequestHandler = async ({ + platform = { env: { DB: {} as D1Database } }, + request +}) => { + try { + if (!PRIVATE_SERVER_KEY) { + return json({ error: 'Server DID is not configured', success: false }, { status: 500 }); + } + + if (!keypair) { + keypair = ucans.EdKeypair.fromSecretKey(PRIVATE_SERVER_KEY); + } + + const authResult = await authenticateRequest(platform, request, { parseBody: true }); + + if (!authResult.success) { + return json({ error: authResult.error, success: false }, { status: authResult.status }); + } + + const { db, xPublicKey } = authResult.data; + + const userId = await getUserIdByPublicKey(db, xPublicKey); + + if (!userId) { + return json( + { error: 'User not found or public key not linked to any user', success: false }, + { status: 404 } + ); + } + + const publicDid = 'did:key:z' + xPublicKey; + + const ucan = await ucans.build({ + issuer: keypair, + audience: publicDid, + lifetimeInSeconds: 60 * 60, + capabilities: [ + emailCapability('create', publicDid), + emailCapability('read', publicDid), + emailCapability('delete', publicDid) + ] + }); + + const token = ucans.encode(ucan); + + return json({ data: { token }, success: true }, { status: 201 }); + } catch (error) { + console.log('error', error); + console.error('Error processing POST request:', error); + return json({ error: 'Internal Server Error', success: false }, { status: 500 }); + } +}; diff --git a/src/routes/ucan/generate/+page.svelte b/src/routes/ucan/generate/+page.svelte new file mode 100644 index 0000000..d046aab --- /dev/null +++ b/src/routes/ucan/generate/+page.svelte @@ -0,0 +1,71 @@ + + +
+
+

Generate UCAN Token

+
+ +
+
+

Generated UCAN Token: {ucanToken}

+ {#if ucanToken} + + {/if} + {#if errorMessage} +

{errorMessage}

+ {/if} +
+
+ Home +
+
+
diff --git a/wrangler.jsonc b/wrangler.jsonc index d742a93..463034b 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -5,7 +5,7 @@ { "binding": "DB", "database_name": "did-ucan-preview", - "database_id": "03a36e46-bc90-4109-8581-b31719d3dc27", + "database_id": "35c2fcf1-7ae4-483a-8254-5d212230b721", "migrations_dir": "drizzle" } ], @@ -15,7 +15,7 @@ { "binding": "DB", "database_name": "did-ucan", - "database_id": "aa853f2b-3636-4aa4-844e-e6a084f0ac5d", + "database_id": "b23afdcf-4098-427c-8c25-eea38a3ceb32", "migrations_dir": "drizzle" } ]