Skip to content

Commit b96a1a7

Browse files
authored
feat: otp provider setup with cicd and terraform (#440)
* fix: local development and build funcs * feat: otp provider with database * fix: issues with the migration files not being compiled * fix: removed unwanted volume from docker compose * feat: terrafrom project and minor db updates * feat: update image name in gh action * feat: add permissions to gh action * feat: add working directory to find dockerfile * feat: run terraform in gh action * feat: setup terraform * feat: add custom domain name * feat: enable db http endpoint for query editor to work * feat: enable terraform apply * feat: remove feature branch name * fix: remove terraform lock file * fix: upgrade tflint * feat: use env var for cors origins
1 parent 0acb427 commit b96a1a7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3440
-251
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: Create and publish Otp Provider Docker image
2+
3+
on:
4+
push:
5+
branches:
6+
- 'dev'
7+
8+
env:
9+
IMAGE_NAME: bcgov-sso/otp-provider
10+
TF_VERSION: 1.2.0
11+
12+
jobs:
13+
build-and-push-image:
14+
permissions: write-all
15+
runs-on: ubuntu-24.04
16+
17+
steps:
18+
- name: Set env to development
19+
if: (github.ref == 'refs/heads/dev' && github.event_name == 'push')
20+
run: |
21+
cat >> $GITHUB_ENV <<EOF
22+
TF_STATE_BUCKET=xgr00q-dev-sso-otp-provider
23+
TF_STATE_BUCKET_KEY=sso-otp-provider.tfstate
24+
TF_STATE_DYNAMODB_TABLE=xgr00q-dev-otp-state-locking
25+
CUSTOM_DOMAIN_NAME=otp-sandbox.loginproxy.gov.bc.ca
26+
CORS_ORIGINS=https://dev.sandbox.loginproxy.gov.bc.ca,https://test.sandbox.loginproxy.gov.bc.ca,https://sandbox.loginproxy.gov.bc.ca,https://sso-playground.apps.gold.devops.gov.bc.ca
27+
28+
EOF
29+
- name: Checkout repository
30+
uses: actions/checkout@v3
31+
32+
- uses: hashicorp/setup-terraform@v3
33+
34+
- name: Configure AWS Credentials
35+
uses: aws-actions/configure-aws-credentials@v4
36+
with:
37+
role-to-assume: ${{ secrets.DEV_OTP_TF_DEPLOY_ROLE_ARN }}
38+
aws-region: ca-central-1
39+
40+
- name: Login to Amazon ECR
41+
id: login-ecr
42+
uses: aws-actions/amazon-ecr-login@v2
43+
44+
- name: Build, tag, and push docker image to Amazon ECR
45+
env:
46+
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
47+
REPOSITORY: ${{ env.IMAGE_NAME }}
48+
IMAGE_TAG: latest
49+
run: |
50+
echo "Building and pushing Docker image to ECR..."
51+
docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG .
52+
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
53+
working-directory: ./docker/otp-provider
54+
55+
- name: Terraform Variables
56+
run: |
57+
cat >"config.tf" <<EOF
58+
terraform {
59+
backend "s3" {
60+
bucket = "${{ env.TF_STATE_BUCKET }}"
61+
key = "${{ env.TF_STATE_BUCKET_KEY }}"
62+
region = "ca-central-1"
63+
dynamodb_table = "${{ env.TF_STATE_DYNAMODB_TABLE }}"
64+
}
65+
}
66+
EOF
67+
68+
cat >"ci.auto.tfvars" <<EOF
69+
aws_ecr_uri="${{ steps.login-ecr.outputs.registry }}"
70+
ches_username="${{ secrets.CHES_USERNAME }}"
71+
ches_password="${{ secrets.CHES_PASSWORD }}"
72+
custom_domain_name="${{ env.CUSTOM_DOMAIN_NAME }}"
73+
cors_origins="${{ env.CORS_ORIGINS }}"
74+
EOF
75+
76+
working-directory: ./docker/otp-provider/terraform
77+
78+
- name: Terraform Init
79+
id: init
80+
run: terraform init -upgrade
81+
working-directory: ./docker/otp-provider/terraform
82+
83+
- name: Terraform Plan
84+
id: plan
85+
run: terraform plan -no-color
86+
working-directory: ./docker/otp-provider/terraform
87+
continue-on-error: true
88+
89+
- name: Terraform Plan Status
90+
if: steps.plan.outcome == 'failure'
91+
run: exit 1
92+
93+
- name: Terraform Apply
94+
if: github.event_name == 'push'
95+
run: terraform apply -auto-approve
96+
working-directory: ./docker/otp-provider/terraform

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,5 @@ mds.json
5858
__pycache__
5959
site
6060
venv
61+
build
62+
secrets.auto.tfvars

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ yarn 1.22.4
77
k6 0.34.1
88
terraform 1.2.0
99
terraform-docs 0.12.1
10-
tflint 0.28.1
10+
tflint 0.43.0
1111
java openjdk-21.0.1
1212
gradle 7.3.1
1313
postgres 14.2

docker/otp-provider/.env.example

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1+
DB_NAME=
2+
DB_USERNAME=
3+
DB_PASSWORD=
4+
DB_HOSTNAME=localhost
5+
DB_PORT=5432
6+
NODE_ENV=development
7+
APP_URL=http://localhost:8080
18
CHES_USERNAME=
29
CHES_PASSWORD=
3-
CHES_EMAIL_URL=
10+
CHES_API_URL=
411
CHES_TOKEN_URL=
512
LOG_LEVEL=info
13+
COOKIE_SECRETS="s3cr3t1,s3cr3t1,s3cr3t2"
14+
JWKS=
15+
CORS_ORIGINS=*

docker/otp-provider/.tool-versions

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
nodejs 22.15.0
2+
yarn 1.22.22
3+
postgres 15.13
4+
tflint 0.43.0

docker/otp-provider/Dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM node:22-alpine
2+
3+
# install yarn
4+
RUN apk add --no-cache yarn
5+
6+
# Set the working directory
7+
WORKDIR /app
8+
9+
# Copy the rest of the application code
10+
COPY . .
11+
12+
# Install dependencies
13+
RUN yarn install
14+
15+
# Build the application
16+
RUN yarn build
17+
18+
ENTRYPOINT [ "yarn", "start" ]

docker/otp-provider/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,55 @@
11
# One Time Password (OTP) Provider
22

3+
The One-Time Password (OTP) identity provider offers a low-assurance, passwordless authentication method. It verifies end-users by sending a temporary code to their email address and validating it against the code entered by the user.
4+
5+
## Architecture Overview
6+
7+
Client Application -> Authenticates via Keycloak
8+
Keycloak (Broker) -> Delegates authentication to Email OTP Identity Provider
9+
OTP Provider -> Authenticates user via email-based OTP and returns identity assertion
10+
11+
## Authentication Flow
12+
13+
- User accesses the client application, which redirects to Keycloak for authentication.
14+
- Keycloak presents the Email OTP provider as a login option (configured as an external identity provider).
15+
- User selects the OTP provider, and Keycloak redirects the user to the provider’s login page.
16+
- User enters their email address, and the provider sends an OTP to that address.
17+
- User enters the OTP, and the provider validates it.
18+
- Upon success, the provider redirects back to Keycloak with an identity token (OIDC or SAML).
19+
- Keycloak maps the external identity to a local user (via mappers or first-login flow).
20+
- Keycloak issues its own tokens (access, ID, refresh) to the client application.
21+
322
## Installation
423

524
- Create `.env` from `.env.example` and update all the values
625
- Run `yarn` to install all the dependencies
726
- Run `yarn dev` to start a local server
27+
- Run `yarn build` to create a javascript bundle for production deployment
28+
- Run `yarn start` to run the javascript bundle
29+
30+
## Test Data
31+
32+
- Given the application and database are up and database migration is successfully complete, run below SQL to add a client for testing purposes.
33+
```sql
34+
INSERT
35+
INTO
36+
"ClientConfig" ("clientId",
37+
"clientSecret",
38+
"grantTypes",
39+
"redirectUris",
40+
"scope",
41+
"responseTypes",
42+
"clientUri",
43+
"postLogoutRedirectUris")
44+
VALUES('conf-client',
45+
's3cr3t',
46+
'{authorization_code, refresh_token}',
47+
'{http://localhost:3001}',
48+
'openid email',
49+
'{code}',
50+
'http://localhost:3001',
51+
'{http://localhost:3001}');
52+
```
853

954
## References
1055

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
version: '3.9'
2+
services:
3+
otp-db:
4+
image: postgres:15
5+
container_name: otp-db
6+
environment:
7+
POSTGRES_USER: postgres
8+
POSTGRES_PASSWORD: postgres
9+
POSTGRES_DB: otp
10+
ports:
11+
- '5432:5432'
12+
volumes:
13+
- otp-db-data:/var/lib/postgresql/data
14+
networks:
15+
- sso-otp-network
16+
otp-provider:
17+
image: sso-otp-provider:dev
18+
container_name: otp-provider
19+
environment:
20+
DB_HOSTNAME: otp-db
21+
DB_PORT: 5432
22+
DB_USERNAME: postgres
23+
DB_PASSWORD: postgres
24+
DB_NAME: otp
25+
NODE_ENV: development
26+
APP_URL: http://localhost:8080
27+
CHES_EMAIL_URL: https://ches.api.gov.bc.ca/api/v1/email
28+
CHES_TOKEN_URL: https://loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token
29+
LOG_LEVEL: debug
30+
COOKIE_SECRETS: 's3cr3t1,s3cr3t1,s3cr3t2'
31+
ports:
32+
- '8080:8080'
33+
depends_on:
34+
- otp-db
35+
networks:
36+
- sso-otp-network
37+
38+
volumes:
39+
otp-db-data:
40+
driver: local
41+
42+
networks:
43+
sso-otp-network: {}

docker/otp-provider/nodemon.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"watch": ["src"],
2+
"watch": ["src", ".env"],
33
"ext": "ts",
44
"ignore": ["build"],
5-
"exec": "ts-node --esm src/server.ts"
5+
"exec": "tsx --env-file=.env src/server.ts"
66
}

docker/otp-provider/package.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,28 @@
55
"license": "MIT",
66
"type": "module",
77
"scripts": {
8-
"build": "npx tsc -p .",
9-
"start:prod": "node .",
8+
"build": "tsup --config src/tsup.config.ts",
9+
"start": "node ./build/server.js",
1010
"dev": "nodemon"
1111
},
1212
"dependencies": {
1313
"axios": "^1.8.4",
14-
"body-parser": "^2.2.0",
1514
"cors": "^2.8.5",
1615
"crypto": "^1.0.1",
1716
"desm": "^1.3.1",
1817
"dotenv": "^16.4.7",
1918
"ejs": "^3.1.10",
2019
"express": "^4.21.2",
20+
"express-rate-limit": "^7.5.0",
2121
"express-session": "^1.18.1",
22+
"helmet": "^8.1.0",
2223
"https": "^1.0.0",
24+
"keygrip": "^1.1.0",
2325
"lodash.isempty": "^4.4.0",
2426
"oidc-provider": "^8.8.1",
27+
"pg": "^8.16.0",
28+
"sequelize": "^6.37.7",
29+
"umzug": "^3.8.2",
2530
"winston": "^3.17.0"
2631
},
2732
"devDependencies": {
@@ -31,6 +36,8 @@
3136
"@types/morgan": "^1.9.9",
3237
"@types/nodemailer": "^6.4.17",
3338
"@types/oidc-provider": "^8.8.1",
39+
"@types/pg": "^8.15.2",
40+
"esbuild": "^0.25.4",
3441
"eslint": "^9.23.0",
3542
"eslint-config-prettier": "^10.1.1",
3643
"eslint-plugin-node": "^11.1.0",
@@ -39,6 +46,8 @@
3946
"nodemon": "^3.1.9",
4047
"prettier": "^3.5.3",
4148
"ts-node": "^10.9.2",
49+
"tsup": "^8.5.0",
50+
"tsx": "^4.19.4",
4251
"typescript": "^5.8.2"
4352
}
4453
}

0 commit comments

Comments
 (0)