Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staging branch #1

Merged
merged 13 commits into from
Aug 20, 2024
47 changes: 47 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Deploy

on: [push]

jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.1.20

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Deploy to production
if: ${{ github.ref == 'refs/heads/main' }}
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env production
environment: production
secrets: |
DENTITY_CLIENT_SECRET
DENTITY_CLIENT_ID
env:
DENTITY_CLIENT_ID: ${{ secrets.DENTITY_CLIENT_ID }}
DENTITY_CLIENT_SECRET: ${{ secrets.DENTITY_CLIENT_SECRET }}

- name: Deploy to staging
if: ${{ github.ref != 'refs/heads/main' }}
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env staging
environment: staging
secrets: |
DENTITY_CLIENT_SECRET
DENTITY_CLIENT_ID
env:
DENTITY_CLIENT_ID: ${{ secrets.DENTITY_STAGING_CLIENT_ID }}
DENTITY_CLIENT_SECRET: ${{ secrets.DENTITY_STAGING_CLIENT_SECRET }}
Binary file modified bun.lockb
Binary file not shown.
19 changes: 11 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
{
"name": "router-ts-worker",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' vitest run"
},
"devDependencies": {
"@biomejs/biome": "1.5.3",
"@cloudflare/workers-types": "^4.20240222.0",
"itty-router": "^4.0.27",
"miniflare": "^3.20240223.0",
"msw": "^2.3.4",
"typescript": "^5.3.3",
"vitest": "^1.3.1",
"vitest-environment-miniflare": "^2.14.2",
"wrangler": "^3.0.0"
},
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev -e staging",
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' vitest run"
},
"type": "module",
"dependencies": {
"itty-router": "4.0.27"
}
}
8 changes: 4 additions & 4 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ test('v1 example handler for v1 example request', async () => {
{} as Env,
{} as ExecutionContext,
)
expect(v1.example).toHaveBeenCalled()
expect(v1.fetchDentityFederatedToken).toHaveBeenCalled()
expect(await response.text()).toBe('Hello, World!')
})

test('head handler for head request', async () => {
vi.mocked(v1.example).mockImplementation(() => {
vi.mocked(v1.fetchDentityFederatedToken).mockImplementation(() => {
const response = new Response('Hello, World!')
response.headers.set('Content-Type', 'application/json')
response.headers.set('Custom-Header', 'HeaderValue')
Expand All @@ -51,7 +51,7 @@ test('head handler for head request', async () => {
{} as Env,
{} as ExecutionContext,
)
expect(v1.example).toHaveBeenCalled()
expect(v1.fetchDentityFederatedToken).toHaveBeenCalled()
expect(response.status).toBe(200)
expect(response.body).toBe(null)
expect(response.headers.get('Content-Type')).toBe('application/json')
Expand Down Expand Up @@ -109,7 +109,7 @@ test('not found for unsupported path', async () => {
})

test('500 error+cors for internal error', async () => {
vi.mocked(v1.example).mockImplementation(() => {
vi.mocked(v1.fetchDentityFederatedToken).mockImplementation(() => {
throw new Error('test')
})
const response = await index.fetch(
Expand Down
11 changes: 6 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ const router = Router<IRequestStrict, [RouteParameters]>()
router.all('*', preflight)

// V1 Routes
router.get('/v1/example', v1.example)
router.head('/v1/example', v1.example)
router.options('/v1/example', () => new Response(null, { status: 204 }))
router.post('/v1/dentity/token', v1.fetchDentityFederatedToken)
router.head('/v1/dentity/token', v1.fetchDentityFederatedToken)
router.options('/v1/dentity/token', () => new Response(null, { status: 204 }))

// 404 Fallback
router.all('*', () => error(404, 'Not Found'))
Expand All @@ -30,9 +30,10 @@ export default {
router
.handle(request, { env, ctx })
.then(stripBodyForHeadRequest(request))
.catch((e) => {
console.error('Caught error')
.catch((e: unknown) => {
console.error(e)
const errorMsg = e instanceof Error ? e.message : ''
if (errorMsg) return error(400, errorMsg)
return error(500, 'Internal Server Error')
})
.then(corsify),
Expand Down
4 changes: 2 additions & 2 deletions src/routes/v1.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { example } from './v1/example.js'
import { fetchDentityFederatedToken } from './v1/dentity/fetchDentityFederatedToken.js'

export const v1 = {
example,
fetchDentityFederatedToken,
}
40 changes: 40 additions & 0 deletions src/routes/v1/dentity/fetchDentityFederatedToken.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'
import { fetchDentityFederatedToken } from './fetchDentityFederatedToken.js'
import { setupServer } from 'msw/node'
import { http, HttpResponse} from 'msw'

const restHandlers = [
http.post('https://example.com/oidc/token', () => {
return HttpResponse.json({
"access_token": "access-token",
"expires_in": 86400,
"id_token": "id-token",
"scope": "openid federated_token",
"token_type": "Bearer",
"federated_token": "federated-token",
"ens_name": "name.eth",
"eth_address": "0xaddress"
})
}),
]

const server = setupServer(...restHandlers)

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))

afterAll(() => server.close())

afterEach(() => server.resetHandlers())

test('works', async () => {
const request = new Request('http://localhost/v1/example', { body: JSON.stringify({ code: 'test-code' }), method: 'POST' })
const response = await fetchDentityFederatedToken(request, { env: {
DENTITY_CLIENT_ID: 'test-client-id',
DENTITY_CLIENT_SECRET: 'test-client-secret',
DENTITY_BASE_ENDPOINT: 'https://example.com',
APP_REDIRECT: 'https://example.com',
}, ctx: {} as ExecutionContext })
expect(response.status).toBe(200)
// expect(response.headers.get('Content-Type')).toMatchInlineSnapshot(`"application/json; charset=utf-8"`)
expect(await response.json()).toEqual({ name: 'name.eth', address: '0xaddress', token: 'federated-token' })
})
48 changes: 48 additions & 0 deletions src/routes/v1/dentity/fetchDentityFederatedToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { json } from "itty-router/json"

import type { RouteParameters } from "@/types.js"
import { error } from "itty-router"

type DentityFederatedTokenResponse = {
"access_token": string,
"expires_in": number,
"id_token": string,
"scope": "openid federated_token",
"token_type": "Bearer",
"federated_token": string,
"ens_name": string,
"eth_address": string
error?: string
error_description?: string
}

export const fetchDentityFederatedToken = async (
_request: Request,
{ env: _env, ctx: _ctx }: RouteParameters,
) => {
const { DENTITY_CLIENT_ID, DENTITY_CLIENT_SECRET, DENTITY_BASE_ENDPOINT, APP_REDIRECT } = _env
const { code } = (await _request.json()) as { code: string }

const body = new URLSearchParams()
body.append("client_id", DENTITY_CLIENT_ID)
body.append("client_secret", DENTITY_CLIENT_SECRET)
body.append("grant_type", "authorization_code")
body.append("code", code)
body.append("redirect_uri", APP_REDIRECT)

const resp = await fetch(`${DENTITY_BASE_ENDPOINT}/oidc/token`, { method: 'POST', body, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
const data = await resp.json()

const { federated_token, ens_name, error: errorTitle, error_description, ...rest} = data as DentityFederatedTokenResponse

if (!federated_token || !ens_name) return error(400, {error : errorTitle, error_description})

const url = new URL(`${DENTITY_BASE_ENDPOINT}/oidc/vp-token`)
url.searchParams.append('federated_token', federated_token)
url.searchParams.append('ens_name', ens_name)

return json({
name: ens_name,
verifiedPresentationUri: url.toString(),
})
}
10 changes: 0 additions & 10 deletions src/routes/v1/example.test.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/routes/v1/example.ts

This file was deleted.

18 changes: 4 additions & 14 deletions worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
interface Env {
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
// MY_KV_NAMESPACE: KVNamespace;
//
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
// MY_DURABLE_OBJECT: DurableObjectNamespace;
//
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
// MY_BUCKET: R2Bucket;
//
// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
// MY_SERVICE: Fetcher;
//
// Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/
// MY_QUEUE: Queue;
APP_REDIRECT: string;
DENTITY_CLIENT_ID: string;
DENTITY_CLIENT_SECRET: string;
DENTITY_BASE_ENDPOINT: string;
}
56 changes: 11 additions & 45 deletions wrangler.toml
Original file line number Diff line number Diff line change
@@ -1,51 +1,17 @@
name = "router-ts-worker"
name = "auth-worker"
main = "src/index.ts"
compatibility_date = "2024-02-23"

# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
# Note: Use secrets to store sensitive data.
# Docs: https://developers.cloudflare.com/workers/platform/environment-variables
# [vars]
# MY_VARIABLE = "production_value"
[env.production]
name = "auth-worker"

# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
# Docs: https://developers.cloudflare.com/workers/runtime-apis/kv
# [[kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
[env.production.vars]
DENTITY_BASE_ENDPOINT = "https://oidc.dentity.com"
APP_REDIRECT = "https://ens.domains"

# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
# Docs: https://developers.cloudflare.com/r2/api/workers/workers-api-usage/
# [[r2_buckets]]
# binding = "MY_BUCKET"
# bucket_name = "my-bucket"
[env.staging]
name = "auth-worker-staging"

# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
# Docs: https://developers.cloudflare.com/queues/get-started
# [[queues.producers]]
# binding = "MY_QUEUE"
# queue = "my-queue"

# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them.
# Docs: https://developers.cloudflare.com/queues/get-started
# [[queues.consumers]]
# queue = "my-queue"

# Bind another Worker service. Use this binding to call another Worker without network overhead.
# Docs: https://developers.cloudflare.com/workers/platform/services
# [[services]]
# binding = "MY_SERVICE"
# service = "my-service"

# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
# Docs: https://developers.cloudflare.com/workers/runtime-apis/durable-objects
# [[durable_objects.bindings]]
# name = "MY_DURABLE_OBJECT"
# class_name = "MyDurableObject"

# Durable Object migrations.
# Docs: https://developers.cloudflare.com/workers/learning/using-durable-objects#configure-durable-object-classes-with-migrations
# [[migrations]]
# tag = "v1"
# new_classes = ["MyDurableObject"]
[env.staging.vars]
DENTITY_BASE_ENDPOINT = "https://oidc.staging.dentity.com"
APP_REDIRECT = "https://dentity-integration.ens-app-v3.pages.dev"