@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.
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +6 -52
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +40 -57
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/rollback.d.ts.map +1 -1
- package/dist/commands/rollback.js +10 -35
- package/dist/commands/rollback.js.map +1 -1
- package/dist/commands/sample.d.ts +2 -0
- package/dist/commands/sample.d.ts.map +1 -1
- package/dist/commands/sample.js +140 -12
- package/dist/commands/sample.js.map +1 -1
- package/dist/commands/versions.d.ts.map +1 -1
- package/dist/commands/versions.js +10 -56
- package/dist/commands/versions.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +58 -12
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/watch.d.ts +26 -0
- package/dist/lib/watch.d.ts.map +1 -0
- package/dist/lib/watch.js +136 -0
- package/dist/lib/watch.js.map +1 -0
- package/dist/templates/nextjs-oauth/.dockerignore +6 -0
- package/dist/templates/nextjs-oauth/.env.example +12 -0
- package/dist/templates/nextjs-oauth/Dockerfile +51 -0
- package/dist/templates/nextjs-oauth/README.md +115 -0
- package/dist/templates/nextjs-oauth/catalog-info.yaml +17 -0
- package/dist/templates/nextjs-oauth/helm/Chart.yaml +6 -0
- package/dist/templates/nextjs-oauth/helm/templates/_helpers.tpl +49 -0
- package/dist/templates/nextjs-oauth/helm/templates/configmap.yaml +10 -0
- package/dist/templates/nextjs-oauth/helm/templates/deployment.yaml +56 -0
- package/dist/templates/nextjs-oauth/helm/templates/ingress.yaml +41 -0
- package/dist/templates/nextjs-oauth/helm/templates/service.yaml +15 -0
- package/dist/templates/nextjs-oauth/helm/values.yaml +69 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/Chart.yaml +9 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/templates/deployment.yaml +68 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/templates/service.yaml +17 -0
- package/dist/templates/nextjs-oauth/helm/{{name}}/values.yaml +51 -0
- package/dist/templates/nextjs-oauth/next.config.js +23 -0
- package/dist/templates/nextjs-oauth/package.json +30 -0
- package/dist/templates/nextjs-oauth/public/.gitkeep +0 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/callback/route.ts +77 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/login/route.ts +16 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/logout/route.ts +7 -0
- package/dist/templates/nextjs-oauth/src/app/api/auth/session/route.ts +42 -0
- package/dist/templates/nextjs-oauth/src/app/api/example/route.ts +63 -0
- package/dist/templates/nextjs-oauth/src/app/api/health/route.ts +10 -0
- package/dist/templates/nextjs-oauth/src/app/dashboard/page.tsx +92 -0
- package/dist/templates/nextjs-oauth/src/app/layout.tsx +18 -0
- package/dist/templates/nextjs-oauth/src/app/page.tsx +110 -0
- package/dist/templates/nextjs-oauth/src/lib/auth.ts +285 -0
- package/dist/templates/nextjs-oauth/src/middleware.ts +71 -0
- package/dist/templates/nextjs-oauth/tsconfig.json +26 -0
- package/dist/types/index.d.ts +9 -5
- package/dist/types/index.d.ts.map +1 -1
- 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,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,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,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
|
+
}
|