@insureco/cli 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/commands/deploy.d.ts.map +1 -1
  2. package/dist/commands/deploy.js +6 -52
  3. package/dist/commands/deploy.js.map +1 -1
  4. package/dist/commands/push.d.ts.map +1 -1
  5. package/dist/commands/push.js +40 -57
  6. package/dist/commands/push.js.map +1 -1
  7. package/dist/commands/rollback.d.ts.map +1 -1
  8. package/dist/commands/rollback.js +10 -35
  9. package/dist/commands/rollback.js.map +1 -1
  10. package/dist/commands/sample.d.ts +2 -0
  11. package/dist/commands/sample.d.ts.map +1 -1
  12. package/dist/commands/sample.js +140 -12
  13. package/dist/commands/sample.js.map +1 -1
  14. package/dist/commands/versions.d.ts.map +1 -1
  15. package/dist/commands/versions.js +10 -56
  16. package/dist/commands/versions.js.map +1 -1
  17. package/dist/index.js +2 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/lib/builder.d.ts.map +1 -1
  20. package/dist/lib/builder.js +58 -12
  21. package/dist/lib/builder.js.map +1 -1
  22. package/dist/lib/watch.d.ts +26 -0
  23. package/dist/lib/watch.d.ts.map +1 -0
  24. package/dist/lib/watch.js +136 -0
  25. package/dist/lib/watch.js.map +1 -0
  26. package/dist/templates/nextjs-oauth/.dockerignore +6 -0
  27. package/dist/templates/nextjs-oauth/.env.example +12 -0
  28. package/dist/templates/nextjs-oauth/Dockerfile +51 -0
  29. package/dist/templates/nextjs-oauth/README.md +115 -0
  30. package/dist/templates/nextjs-oauth/catalog-info.yaml +17 -0
  31. package/dist/templates/nextjs-oauth/helm/Chart.yaml +6 -0
  32. package/dist/templates/nextjs-oauth/helm/templates/_helpers.tpl +49 -0
  33. package/dist/templates/nextjs-oauth/helm/templates/configmap.yaml +10 -0
  34. package/dist/templates/nextjs-oauth/helm/templates/deployment.yaml +56 -0
  35. package/dist/templates/nextjs-oauth/helm/templates/ingress.yaml +41 -0
  36. package/dist/templates/nextjs-oauth/helm/templates/service.yaml +15 -0
  37. package/dist/templates/nextjs-oauth/helm/values.yaml +69 -0
  38. package/dist/templates/nextjs-oauth/helm/{{name}}/Chart.yaml +9 -0
  39. package/dist/templates/nextjs-oauth/helm/{{name}}/templates/deployment.yaml +68 -0
  40. package/dist/templates/nextjs-oauth/helm/{{name}}/templates/service.yaml +17 -0
  41. package/dist/templates/nextjs-oauth/helm/{{name}}/values.yaml +51 -0
  42. package/dist/templates/nextjs-oauth/next.config.js +23 -0
  43. package/dist/templates/nextjs-oauth/package.json +30 -0
  44. package/dist/templates/nextjs-oauth/public/.gitkeep +0 -0
  45. package/dist/templates/nextjs-oauth/src/app/api/auth/callback/route.ts +77 -0
  46. package/dist/templates/nextjs-oauth/src/app/api/auth/login/route.ts +16 -0
  47. package/dist/templates/nextjs-oauth/src/app/api/auth/logout/route.ts +7 -0
  48. package/dist/templates/nextjs-oauth/src/app/api/auth/session/route.ts +42 -0
  49. package/dist/templates/nextjs-oauth/src/app/api/example/route.ts +63 -0
  50. package/dist/templates/nextjs-oauth/src/app/api/health/route.ts +10 -0
  51. package/dist/templates/nextjs-oauth/src/app/dashboard/page.tsx +92 -0
  52. package/dist/templates/nextjs-oauth/src/app/layout.tsx +18 -0
  53. package/dist/templates/nextjs-oauth/src/app/page.tsx +110 -0
  54. package/dist/templates/nextjs-oauth/src/lib/auth.ts +285 -0
  55. package/dist/templates/nextjs-oauth/src/middleware.ts +71 -0
  56. package/dist/templates/nextjs-oauth/tsconfig.json +26 -0
  57. package/dist/types/index.d.ts +9 -5
  58. package/dist/types/index.d.ts.map +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,115 @@
1
+ # {{name}}
2
+
3
+ {{description}}
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ # 1. Create an OAuth app (one-time setup)
9
+ iec oauth create "{{name}}" "http://localhost:3000/api/auth/callback"
10
+ # Save the CLIENT_ID and CLIENT_SECRET — the secret is shown only once
11
+
12
+ # 2. Configure environment
13
+ cp .env.example .env
14
+ # Paste OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET into .env
15
+ # Generate a JWT_SECRET:
16
+ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
17
+
18
+ # 3. Run locally
19
+ npm install
20
+ npm run dev
21
+ # Open http://localhost:3000 → Sign In → you'll be redirected to Bio-id
22
+ ```
23
+
24
+ ## Deploy to Tawa Platform
25
+
26
+ ```bash
27
+ # Add the sandbox redirect URI to your OAuth app
28
+ iec oauth add-uri <CLIENT_ID> "https://{{name}}.sandbox.tawa.insureco.io/api/auth/callback"
29
+
30
+ # Register service and deploy
31
+ iec register
32
+ iec deploy
33
+ ```
34
+
35
+ Set the environment variables in your Helm values (`helm/{{name}}/values.yaml`) or via the builder's env config.
36
+
37
+ ## How Authentication Works
38
+
39
+ This app uses **Bio-id OAuth2 with PKCE**:
40
+
41
+ 1. User clicks **Sign In** → redirected to Bio-id
42
+ 2. Bio-id authenticates the user and redirects back with an authorization code
43
+ 3. `/api/auth/callback` exchanges the code for tokens, fetches user info from Bio-id
44
+ 4. A **session JWT** is created (signed with your `JWT_SECRET`) and stored as an httpOnly cookie
45
+ 5. Middleware and server components verify the session JWT on protected routes
46
+
47
+ The app never stores Bio-id's access token in cookies. Instead, it creates its own session after validating the user through Bio-id's userinfo endpoint.
48
+
49
+ ### Auth Endpoints
50
+
51
+ | Route | Method | Description |
52
+ |-------|--------|-------------|
53
+ | `/api/auth/login` | GET | Starts OAuth flow (redirects to Bio-id) |
54
+ | `/api/auth/callback` | GET | Handles redirect, creates session |
55
+ | `/api/auth/logout` | GET/POST | Clears session cookies |
56
+ | `/api/auth/session` | GET | Returns current user as JSON (with auto-refresh) |
57
+
58
+ ### Protecting Routes
59
+
60
+ **Middleware** (`src/middleware.ts`) guards `/dashboard` — add more paths to `PROTECTED_PATHS`.
61
+
62
+ **Server components** use `getCurrentUser()`:
63
+
64
+ ```tsx
65
+ import { getCurrentUser } from '@/lib/auth'
66
+
67
+ export default async function ProtectedPage() {
68
+ const user = await getCurrentUser()
69
+ if (!user) redirect('/api/auth/login')
70
+ return <p>Hello {user.name}</p>
71
+ }
72
+ ```
73
+
74
+ ## Environment Variables
75
+
76
+ | Variable | Required | Description |
77
+ |----------|----------|-------------|
78
+ | `BIO_ID_URL` | Yes | Bio-id server URL (`https://bio.tawa.insureco.io`) |
79
+ | `APP_URL` | Yes | Your app's URL (`http://localhost:3000` for dev) |
80
+ | `OAUTH_CLIENT_ID` | Yes | From `iec oauth create` |
81
+ | `OAUTH_CLIENT_SECRET` | Yes | From `iec oauth create` (shown once) |
82
+ | `JWT_SECRET` | Yes | Random 32+ byte hex string for signing session JWTs |
83
+ | `NEXT_PUBLIC_APP_NAME` | No | Display name in browser tab |
84
+
85
+ ## Project Structure
86
+
87
+ ```
88
+ {{name}}/
89
+ ├── src/
90
+ │ ├── lib/
91
+ │ │ └── auth.ts # OAuth2 + session management
92
+ │ ├── middleware.ts # Route protection
93
+ │ └── app/
94
+ │ ├── page.tsx # Home page (sign in button)
95
+ │ ├── dashboard/page.tsx # Protected page
96
+ │ └── api/
97
+ │ ├── health/route.ts # Health check (Kubernetes)
98
+ │ ├── example/route.ts # Example API route
99
+ │ └── auth/
100
+ │ ├── login/route.ts
101
+ │ ├── callback/route.ts
102
+ │ ├── logout/route.ts
103
+ │ └── session/route.ts
104
+ ├── helm/{{name}}/ # Kubernetes Helm chart
105
+ ├── Dockerfile
106
+ └── catalog-info.yaml # Backstage service catalog
107
+ ```
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ npm run dev # Start dev server at http://localhost:3000
113
+ npm run build # Production build
114
+ npm start # Start production server
115
+ ```
@@ -0,0 +1,17 @@
1
+ apiVersion: backstage.io/v1alpha1
2
+ kind: Component
3
+ metadata:
4
+ name: {{name}}
5
+ description: {{description}}
6
+ tags:
7
+ - tawa
8
+ - frontend
9
+ - nextjs
10
+ annotations:
11
+ insureco.io/framework: nextjs
12
+ insureco.io/language: typescript
13
+ insureco.io/auth: bio-id
14
+ spec:
15
+ type: service
16
+ lifecycle: experimental
17
+ owner: team-platform
@@ -0,0 +1,6 @@
1
+ apiVersion: v2
2
+ name: {{name}}
3
+ description: Next.js app with Bio-id OAuth2 authentication
4
+ type: application
5
+ version: 0.1.0
6
+ appVersion: "1.0.0"
@@ -0,0 +1,49 @@
1
+ {{/*
2
+ Expand the name of the chart.
3
+ */}}
4
+ {{- define "app.name" -}}
5
+ {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6
+ {{- end }}
7
+
8
+ {{/*
9
+ Create a default fully qualified app name.
10
+ */}}
11
+ {{- define "app.fullname" -}}
12
+ {{- if .Values.fullnameOverride }}
13
+ {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
14
+ {{- else }}
15
+ {{- $name := default .Chart.Name .Values.nameOverride }}
16
+ {{- if contains $name .Release.Name }}
17
+ {{- .Release.Name | trunc 63 | trimSuffix "-" }}
18
+ {{- else }}
19
+ {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
20
+ {{- end }}
21
+ {{- end }}
22
+ {{- end }}
23
+
24
+ {{/*
25
+ Create chart name and version as used by the chart label.
26
+ */}}
27
+ {{- define "app.chart" -}}
28
+ {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
29
+ {{- end }}
30
+
31
+ {{/*
32
+ Common labels
33
+ */}}
34
+ {{- define "app.labels" -}}
35
+ helm.sh/chart: {{ include "app.chart" . }}
36
+ {{ include "app.selectorLabels" . }}
37
+ {{- if .Chart.AppVersion }}
38
+ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
39
+ {{- end }}
40
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
41
+ {{- end }}
42
+
43
+ {{/*
44
+ Selector labels
45
+ */}}
46
+ {{- define "app.selectorLabels" -}}
47
+ app.kubernetes.io/name: {{ include "app.name" . }}
48
+ app.kubernetes.io/instance: {{ .Release.Name }}
49
+ {{- end }}
@@ -0,0 +1,10 @@
1
+ apiVersion: v1
2
+ kind: ConfigMap
3
+ metadata:
4
+ name: {{ include "app.fullname" . }}-config
5
+ labels:
6
+ {{- include "app.labels" . | nindent 4 }}
7
+ data:
8
+ {{- range $key, $value := .Values.env }}
9
+ {{ $key }}: {{ $value | quote }}
10
+ {{- end }}
@@ -0,0 +1,56 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: {{ include "app.fullname" . }}
5
+ labels:
6
+ {{- include "app.labels" . | nindent 4 }}
7
+ spec:
8
+ {{- if not .Values.autoscaling.enabled }}
9
+ replicas: {{ .Values.replicaCount }}
10
+ {{- end }}
11
+ selector:
12
+ matchLabels:
13
+ {{- include "app.selectorLabels" . | nindent 6 }}
14
+ template:
15
+ metadata:
16
+ labels:
17
+ {{- include "app.labels" . | nindent 8 }}
18
+ spec:
19
+ {{- with .Values.imagePullSecrets }}
20
+ imagePullSecrets:
21
+ {{- toYaml . | nindent 8 }}
22
+ {{- end }}
23
+ containers:
24
+ - name: {{ .Chart.Name }}
25
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
26
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
27
+ ports:
28
+ - name: http
29
+ containerPort: {{ .Values.service.port }}
30
+ protocol: TCP
31
+ envFrom:
32
+ - configMapRef:
33
+ name: {{ include "app.fullname" . }}-config
34
+ {{- if .Values.secretRef }}
35
+ - secretRef:
36
+ name: {{ .Values.secretRef }}
37
+ optional: true
38
+ {{- end }}
39
+ livenessProbe:
40
+ {{- toYaml .Values.livenessProbe | nindent 12 }}
41
+ readinessProbe:
42
+ {{- toYaml .Values.readinessProbe | nindent 12 }}
43
+ resources:
44
+ {{- toYaml .Values.resources | nindent 12 }}
45
+ {{- with .Values.nodeSelector }}
46
+ nodeSelector:
47
+ {{- toYaml . | nindent 8 }}
48
+ {{- end }}
49
+ {{- with .Values.tolerations }}
50
+ tolerations:
51
+ {{- toYaml . | nindent 8 }}
52
+ {{- end }}
53
+ {{- with .Values.affinity }}
54
+ affinity:
55
+ {{- toYaml . | nindent 8 }}
56
+ {{- end }}
@@ -0,0 +1,41 @@
1
+ {{- if .Values.ingress.enabled -}}
2
+ apiVersion: networking.k8s.io/v1
3
+ kind: Ingress
4
+ metadata:
5
+ name: {{ include "app.fullname" . }}
6
+ labels:
7
+ {{- include "app.labels" . | nindent 4 }}
8
+ {{- with .Values.ingress.annotations }}
9
+ annotations:
10
+ {{- toYaml . | nindent 4 }}
11
+ {{- end }}
12
+ spec:
13
+ {{- if .Values.ingress.className }}
14
+ ingressClassName: {{ .Values.ingress.className }}
15
+ {{- end }}
16
+ {{- if .Values.ingress.tls }}
17
+ tls:
18
+ {{- range .Values.ingress.tls }}
19
+ - hosts:
20
+ {{- range .hosts }}
21
+ - {{ . | quote }}
22
+ {{- end }}
23
+ secretName: {{ .secretName }}
24
+ {{- end }}
25
+ {{- end }}
26
+ rules:
27
+ {{- range .Values.ingress.hosts }}
28
+ - host: {{ .host | quote }}
29
+ http:
30
+ paths:
31
+ {{- range .paths }}
32
+ - path: {{ .path }}
33
+ pathType: {{ .pathType }}
34
+ backend:
35
+ service:
36
+ name: {{ include "app.fullname" $ }}
37
+ port:
38
+ number: {{ $.Values.service.port }}
39
+ {{- end }}
40
+ {{- end }}
41
+ {{- end }}
@@ -0,0 +1,15 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: {{ include "app.fullname" . }}
5
+ labels:
6
+ {{- include "app.labels" . | nindent 4 }}
7
+ spec:
8
+ type: {{ .Values.service.type }}
9
+ ports:
10
+ - port: {{ .Values.service.port }}
11
+ targetPort: http
12
+ protocol: TCP
13
+ name: http
14
+ selector:
15
+ {{- include "app.selectorLabels" . | nindent 4 }}
@@ -0,0 +1,69 @@
1
+ replicaCount: 1
2
+
3
+ image:
4
+ repository: registry.digitalocean.com/insureco/{{name}}
5
+ pullPolicy: IfNotPresent
6
+ tag: "latest"
7
+
8
+ imagePullSecrets:
9
+ - name: insureco
10
+
11
+ nameOverride: ""
12
+ fullnameOverride: ""
13
+
14
+ service:
15
+ type: ClusterIP
16
+ port: 3000
17
+
18
+ ingress:
19
+ enabled: true
20
+ className: nginx
21
+ annotations:
22
+ nginx.ingress.kubernetes.io/proxy-body-size: "10m"
23
+ nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
24
+ hosts:
25
+ - host: {{name}}.sandbox.tawa.insureco.io
26
+ paths:
27
+ - path: /
28
+ pathType: Prefix
29
+ tls: []
30
+
31
+ resources:
32
+ limits:
33
+ cpu: 500m
34
+ memory: 512Mi
35
+ requests:
36
+ cpu: 100m
37
+ memory: 256Mi
38
+
39
+ autoscaling:
40
+ enabled: false
41
+
42
+ env:
43
+ NODE_ENV: production
44
+ PORT: "3000"
45
+ HOSTNAME: "0.0.0.0"
46
+ BIO_ID_URL: "https://bio.tawa.insureco.io"
47
+
48
+ # K8s Secret containing OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, JWT_SECRET
49
+ # Create with: kubectl create secret generic {{name}}-secrets --from-env-file=.env -n <namespace>
50
+ secretRef: {{name}}-secrets
51
+
52
+ livenessProbe:
53
+ httpGet:
54
+ path: /api/health
55
+ port: http
56
+ initialDelaySeconds: 15
57
+ periodSeconds: 10
58
+ failureThreshold: 3
59
+
60
+ readinessProbe:
61
+ httpGet:
62
+ path: /api/health
63
+ port: http
64
+ initialDelaySeconds: 5
65
+ periodSeconds: 5
66
+
67
+ nodeSelector: {}
68
+ tolerations: []
69
+ affinity: {}
@@ -0,0 +1,9 @@
1
+ apiVersion: v2
2
+ name: {{name}}
3
+ description: Helm chart for {{name}} (Next.js)
4
+ type: application
5
+ version: 0.1.0
6
+ appVersion: "0.1.0"
7
+ maintainers:
8
+ - name: team-platform
9
+ email: platform@insureco.io
@@ -0,0 +1,68 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: {{ .Release.Name }}
5
+ labels:
6
+ app: {{ .Release.Name }}
7
+ app.kubernetes.io/name: {{ .Release.Name }}
8
+ app.kubernetes.io/instance: {{ .Release.Name }}
9
+ spec:
10
+ replicas: {{ .Values.replicaCount }}
11
+ selector:
12
+ matchLabels:
13
+ app: {{ .Release.Name }}
14
+ template:
15
+ metadata:
16
+ labels:
17
+ app: {{ .Release.Name }}
18
+ app.kubernetes.io/name: {{ .Release.Name }}
19
+ app.kubernetes.io/instance: {{ .Release.Name }}
20
+ spec:
21
+ {{- with .Values.imagePullSecrets }}
22
+ imagePullSecrets:
23
+ {{- toYaml . | nindent 8 }}
24
+ {{- end }}
25
+ containers:
26
+ - name: {{ .Chart.Name }}
27
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
28
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
29
+ ports:
30
+ - name: http
31
+ containerPort: {{ .Values.service.port }}
32
+ protocol: TCP
33
+ envFrom:
34
+ - configMapRef:
35
+ name: {{ .Release.Name }}-config
36
+ optional: true
37
+ - secretRef:
38
+ name: {{ .Release.Name }}-secrets
39
+ optional: true
40
+ {{- with .Values.env }}
41
+ env:
42
+ {{- range $key, $value := . }}
43
+ - name: {{ $key }}
44
+ value: {{ $value | quote }}
45
+ {{- end }}
46
+ {{- end }}
47
+ {{- with .Values.livenessProbe }}
48
+ livenessProbe:
49
+ {{- toYaml . | nindent 12 }}
50
+ {{- end }}
51
+ {{- with .Values.readinessProbe }}
52
+ readinessProbe:
53
+ {{- toYaml . | nindent 12 }}
54
+ {{- end }}
55
+ resources:
56
+ {{- toYaml .Values.resources | nindent 12 }}
57
+ {{- with .Values.nodeSelector }}
58
+ nodeSelector:
59
+ {{- toYaml . | nindent 8 }}
60
+ {{- end }}
61
+ {{- with .Values.affinity }}
62
+ affinity:
63
+ {{- toYaml . | nindent 8 }}
64
+ {{- end }}
65
+ {{- with .Values.tolerations }}
66
+ tolerations:
67
+ {{- toYaml . | nindent 8 }}
68
+ {{- end }}
@@ -0,0 +1,17 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: {{ .Release.Name }}
5
+ labels:
6
+ app: {{ .Release.Name }}
7
+ app.kubernetes.io/name: {{ .Release.Name }}
8
+ app.kubernetes.io/instance: {{ .Release.Name }}
9
+ spec:
10
+ type: {{ .Values.service.type }}
11
+ ports:
12
+ - port: {{ .Values.service.port }}
13
+ targetPort: http
14
+ protocol: TCP
15
+ name: http
16
+ selector:
17
+ app: {{ .Release.Name }}
@@ -0,0 +1,51 @@
1
+ replicaCount: 1
2
+
3
+ image:
4
+ repository: registry.digitalocean.com/insureco/{{name}}
5
+ tag: latest
6
+ pullPolicy: IfNotPresent
7
+
8
+ imagePullSecrets:
9
+ - name: do-registry
10
+
11
+ service:
12
+ type: ClusterIP
13
+ port: 3000
14
+
15
+ resources:
16
+ limits:
17
+ cpu: 500m
18
+ memory: 512Mi
19
+ requests:
20
+ cpu: 100m
21
+ memory: 128Mi
22
+
23
+ autoscaling:
24
+ enabled: false
25
+ minReplicas: 1
26
+ maxReplicas: 5
27
+ targetCPUUtilizationPercentage: 80
28
+
29
+ nodeSelector: {}
30
+
31
+ tolerations: []
32
+
33
+ affinity: {}
34
+
35
+ env: {}
36
+
37
+ secrets: {}
38
+
39
+ livenessProbe:
40
+ httpGet:
41
+ path: /api/health
42
+ port: 3000
43
+ initialDelaySeconds: 15
44
+ periodSeconds: 10
45
+
46
+ readinessProbe:
47
+ httpGet:
48
+ path: /api/health
49
+ port: 3000
50
+ initialDelaySeconds: 5
51
+ periodSeconds: 5
@@ -0,0 +1,23 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ output: 'standalone',
4
+ reactStrictMode: true,
5
+ poweredByHeader: false,
6
+
7
+ // Enable experimental features as needed
8
+ // experimental: {
9
+ // serverActions: true,
10
+ // },
11
+
12
+ // Configure allowed image domains
13
+ // images: {
14
+ // remotePatterns: [
15
+ // {
16
+ // protocol: 'https',
17
+ // hostname: '*.insureco.io',
18
+ // },
19
+ // ],
20
+ // },
21
+ }
22
+
23
+ module.exports = nextConfig
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.1.0",
4
+ "description": "{{description}}",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "next dev",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "next lint",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "next": "^14.2.0",
15
+ "react": "^18.3.0",
16
+ "react-dom": "^18.3.0",
17
+ "jose": "^5.9.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.0.0",
21
+ "@types/react": "^18.3.0",
22
+ "@types/react-dom": "^18.3.0",
23
+ "eslint": "^8.0.0",
24
+ "eslint-config-next": "^14.2.0",
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=20.0.0"
29
+ }
30
+ }
File without changes
@@ -0,0 +1,77 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { cookies } from 'next/headers'
3
+ import {
4
+ exchangeCodeForTokens,
5
+ fetchUserInfo,
6
+ createSessionToken,
7
+ setSessionCookies,
8
+ sanitizeReturnTo,
9
+ getAppUrl,
10
+ } from '@/lib/auth'
11
+
12
+ export async function GET(request: NextRequest) {
13
+ const searchParams = request.nextUrl.searchParams
14
+ const code = searchParams.get('code')
15
+ const state = searchParams.get('state')
16
+ const error = searchParams.get('error')
17
+ const errorDescription = searchParams.get('error_description')
18
+
19
+ const cookieStore = await cookies()
20
+ const storedState = cookieStore.get('oauth_state')?.value
21
+ const codeVerifier = cookieStore.get('oauth_code_verifier')?.value
22
+ const returnTo = cookieStore.get('oauth_return_to')?.value || '/dashboard'
23
+
24
+ // Clear OAuth flow cookies
25
+ cookieStore.delete('oauth_state')
26
+ cookieStore.delete('oauth_code_verifier')
27
+ cookieStore.delete('oauth_return_to')
28
+
29
+ // Handle error from authorization server
30
+ if (error) {
31
+ const errorUrl = new URL('/', getAppUrl())
32
+ errorUrl.searchParams.set('error', error)
33
+ if (errorDescription) {
34
+ errorUrl.searchParams.set('error_description', errorDescription)
35
+ }
36
+ return NextResponse.redirect(errorUrl)
37
+ }
38
+
39
+ // Validate state to prevent CSRF
40
+ if (!state || state !== storedState) {
41
+ const errorUrl = new URL('/', getAppUrl())
42
+ errorUrl.searchParams.set('error', 'invalid_state')
43
+ errorUrl.searchParams.set('error_description', 'State parameter mismatch')
44
+ return NextResponse.redirect(errorUrl)
45
+ }
46
+
47
+ // Validate required params
48
+ if (!code || !codeVerifier) {
49
+ const errorUrl = new URL('/', getAppUrl())
50
+ errorUrl.searchParams.set('error', 'invalid_request')
51
+ errorUrl.searchParams.set('error_description', 'Missing authorization code')
52
+ return NextResponse.redirect(errorUrl)
53
+ }
54
+
55
+ try {
56
+ // Exchange code for bio-id tokens
57
+ const tokens = await exchangeCodeForTokens(code, codeVerifier)
58
+
59
+ // Fetch user info from bio-id using the access token
60
+ const user = await fetchUserInfo(tokens.access_token)
61
+
62
+ // Create our own session JWT and store it (not bio-id's access token)
63
+ const sessionToken = await createSessionToken(user)
64
+ await setSessionCookies(sessionToken, tokens.refresh_token)
65
+
66
+ const safeReturnTo = sanitizeReturnTo(returnTo)
67
+ return NextResponse.redirect(new URL(safeReturnTo, getAppUrl()))
68
+ } catch (err) {
69
+ const errorUrl = new URL('/', getAppUrl())
70
+ errorUrl.searchParams.set('error', 'token_exchange_failed')
71
+ errorUrl.searchParams.set(
72
+ 'error_description',
73
+ err instanceof Error ? err.message : 'Failed to exchange authorization code'
74
+ )
75
+ return NextResponse.redirect(errorUrl)
76
+ }
77
+ }
@@ -0,0 +1,16 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { getLoginUrl } from '@/lib/auth'
3
+
4
+ export async function GET(request: NextRequest) {
5
+ const returnTo = request.nextUrl.searchParams.get('returnTo') || '/dashboard'
6
+
7
+ try {
8
+ const loginUrl = await getLoginUrl(returnTo)
9
+ return NextResponse.redirect(loginUrl)
10
+ } catch (error) {
11
+ const message = error instanceof Error ? error.message : 'Login failed'
12
+ return NextResponse.redirect(
13
+ new URL(`/?error=${encodeURIComponent(message)}`, request.url)
14
+ )
15
+ }
16
+ }