Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.json]
indent_size = 2

[*.{yml,yaml}]
indent_size = 2
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* text=auto eol=lf

# Keep Windows shell scripts with CRLF for compatibility.
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
86 changes: 86 additions & 0 deletions .github/workflows/ghp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: GitHub Pages
on:
push:
branches: [ "outlook" ]
workflow_dispatch:
inputs:
VITE_IO_BRIDGE_URL:
description: 'Optional bridge URL override for this run (e.g. https://bridge.example.com)'
required: false
type: string

#concurrency:
# group: pages
# cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write

steps:
- name: Checkout bridge-examples
uses: actions/checkout@v6
with:
path: bridge-examples
- name: Checkout connect-js
uses: actions/checkout@v6
with:
repository: InteropIO/connect-js
ref: feature/upgrade-gateway
token: '${{ secrets.PAT }}'
path: connect-js

- name: Use Node 20 for connect-js build
uses: actions/setup-node@v6
with:
node-version: 20
cache: npm
cache-dependency-path: connect-js/package-lock.json

- name: Build connect-js
working-directory: connect-js/
env:
LICENSE_KEY: ${{ secrets.IO_CB_LICENSE_KEY }}
run: |
npm ci
npm run build

- name: Use project Node version for office build
uses: actions/setup-node@v6
with:
node-version-file: bridge-examples/.node-version
cache: npm
cache-dependency-path: bridge-examples/office/package-lock.json
- name: Install dependencies
working-directory: bridge-examples/office
run: npm ci
- name: Build
working-directory: bridge-examples/office
run: npm run build
env:
VITE_IO_BRIDGE_URL: ${{ github.event.inputs.VITE_IO_BRIDGE_URL || vars.VITE_IO_BRIDGE_URL }}
VITE_AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }}
VITE_AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }}
VITE_AUTH0_AUDIENCE: ${{ secrets.AUTH0_AUDIENCE }}
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v4
with:
path: bridge-examples/office/dist

deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
.env
.secrets/
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.14.0
2 changes: 0 additions & 2 deletions README.md

This file was deleted.

135 changes: 135 additions & 0 deletions office/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
## This is an example .env file for io.Bridge
## Copy this file as .env and fill in the required values

###############################################################################
## License key for io.Bridge ##
###############################################################################

## License key for io.Bridge (Required). Specify it as a string.
IO_BRIDGE_LICENSE_KEY=
## Alternatively, you can specify the path to a file containing the license key.
#IO_BRIDGE_LICENSE_KEY_FILE=./.secrets/io-bridge-license.key

###############################################################################
## Server ##
###############################################################################

## The PORT for io.Bridge server to bind on (Optional, default is 8084)
#IO_BRIDGE_SERVER_PORT=8385

## The host for io.Bridge server to bind on (Optional, default is '0.0.0.0' i.e. any local IP)
#IO_BRIDGE_SERVER_HOST=127.0.0.1

## The path to file where the resolved io.Bridge server is bound (Optional)
#IO_BRIDGE_PRINT_PORT=./io-bridge-address.json

###############################################################################
## Auth ##
###############################################################################

## Authentication type. For example 'none', 'basic' or 'oauth2'
#IO_BRIDGE_SERVER_AUTH_TYPE=oauth2

## Basic Authentication Realm
IO_BRIDGE_SERVER_AUTH_BASIC_REALM=interop.io

##
#IO_BRIDGE_SERVER_AUTH_OAUTH2_JWT_ISSUERURI=

##
IO_BRIDGE_SERVER_AUTH_OAUTH2_JWT_AUDIENCE=
## Same as the one above, but exposed to the client
VITE_AUTH0_AUDIENCE=

## Client ID for Auth0 SPA application
#VITE_AUTH0_CLIENT_ID=

## Domain for Auth0 tenant
#VITE_AUTH0_DOMAIN=

##
#VITE_AUTH0_SCOPES=openid email

###############################################################################
## Mesh ##
###############################################################################

## The ping interval for mesh (Optional, default is 30s)
#IO_BRIDGE_MESH_PING_INTERVAL=30s

###############################################################################
## Gateway ##
###############################################################################

## If you want to embed gateway (Optional, default is false)
#IO_BRIDGE_GATEWAY_ENABLED=true

###############################################################################
## CORS ##
###############################################################################

## Allow origins for CORS (Optional, default is *)
## The special value * allows all origins.
## You can also specify multiple origins separated by commas.
## You can use regular expressions to match origins, for example:
## - http:\/\/localhost(:d+)? matches http://localhost, http://localhost:8080, etc.
## - file:\/\/.* matches any file URL
## - null is a way to support specific 'null' origin set by the client, for example when using about:blank in a browser
#IO_BRIDGE_SERVER_CORS_ALLOW_ORIGIN=/http:\/\/localhost(:d+)?/,/file:\/\/.*/,null

## Allow methods for CORS (Optional, default is GET,HEAD). The special value * allows all methods.
#IO_BRIDGE_SERVER_CORS_ALLOW_METHODS=GET,POST,DELETE

## Which headers a pre-flight request can list as allowed for use during an actual request. (Optional, default is *)
#IO_BRIDGE_SERVER_CORS_ALLOW_HEADERS=*

## Which headers that an actual response might have and can be exposed to the clients (Optional)
#IO_BRIDGE_SERVER_CORS_EXPOSE_HEADERS=X-Authorization

## How long (in seconds) response from a pre-flight request can be cached by clients (Optional, default is 3600 i.e. 1 hour)
#IO_BRIDGE_SERVER_CORS_MAX_AGE=3600

## If you want to allow credentials in CORS (Optional, default is false), cannot be true if allow origins is set to *
## Setting this to true will impact how allow origins, methods and headers are processed
#IO_BRIDGE_SERVER_CORS_ALLOW_CREDENTIALS=true

## If you want to allow private network access (PNA) in CORS (Optional, default is false), cannot be true if allow origins is set to *
#IO_BRIDGE_SERVER_CORS_ALLOW_PRIVATE_NETWORK=true

## If you want to completely disable CORS handling (Optional, default is false)
#IO_BRIDGE_SERVER_CORS_DISABLED=true

###############################################################################
## Logging
###############################################################################

## This is the logging layout to use. The default is 'ecs' which is the Elastic Common Schema.
## Other options are 'logstash'
LOGGING_LAYOUT=ecs

## The logging level to use. The default is 'info'.
## Supported levels are: trace, debug, info, warn, error and off
#LOGGING_LEVEL=debug

#LOGGING_LEVEL_GATEWAY_BRIDGE=debug
#LOGGING_LEVEL_GATEWAY_SERVER=info
#LOGGING_LEVEL_GATEWAY_SERVER_HTTP=debug

LOGGING_LEVEL_GATEWAY=info

###############################################################################
## Web App Configuration (Vite)
## Variables prefixed with VITE_ are exposed to the browser (required by Vite)
###############################################################################

## License key for io.Connect Browser Platform (Required for web app)
## Note: This is a DIFFERENT license than IO_BRIDGE_LICENSE_KEY above.
## - IO_BRIDGE_LICENSE_KEY is for the io.Bridge server (Node.js)
## - VITE_IO_CB_LICENSE_KEY is for the browser platform (client-side)
VITE_IO_CB_LICENSE_KEY=

## io.Bridge URL for browser platform connection (Optional, default is https://localhost:8084)
#VITE_IO_BRIDGE_URL=https://localhost:8084
VITE_AUTH0_DOMAIN=
VITE_AUTH0_CLIENT_ID=
VITE_AUTH0_AUDIENCE=
3 changes: 3 additions & 0 deletions office/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
node_modules/
public/static/modals/
123 changes: 123 additions & 0 deletions office/auth0/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { ServerConfigurer } from '@interopio/gateway-server/web/server';
import type { Logger } from '@interopio/gateway/logging/api';
import { randomUUID } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';

export type AuthInfo = {
user: string
username?: string,
token?: string | null
headers?: Record<string, string>
}

const successPage = readFileSync(resolve(import.meta.dirname, './success.html')).toString();

// ---------------------------------------------------------------------------
// Auth0 integration — Implicit Flow with response_mode=form_post
//
// GET /login/auth0 → redirects to Auth0 Universal Login
// POST /api/webhooks/auth0/callback → Auth0 POSTs tokens back here
// ---------------------------------------------------------------------------
export const app = async (
config: {
logger: Logger,
issuerUrl: string,
clientId: string,
scopes?: string,
audience?: string,
authInfoResolver: (data: AuthInfo) => void,
},
{
handle
}: ServerConfigurer
) => {
const { logger, issuerUrl, clientId } = config;
const scopes = config.scopes ?? 'openid email';
const loginCallbackPath = '/login/oauth2/code/auth0';
const loginUrlPath = '/login/auth0';

logger.info(`auth0 login endpoint registered on [${loginUrlPath}]`);
logger.info(`auth0 callback endpoint registered on [${loginCallbackPath}]`);

handle(
// ---------------------------------------------------------------
// GET /login/auth0 — redirect to Auth0 Universal Login
// ---------------------------------------------------------------
{
request: { method: 'GET', path: loginUrlPath },
options: { authorize: { access: 'permitted' } },
handler: async ({ request, response }) => {

const nonce = randomUUID();
const state = randomUUID();
const redirectUri = `${request.protocol}://${request.host}${loginCallbackPath}`;
logger.info('initiating Auth0 implicit login flow', { redirectUri });

const authorizeUrl = `${issuerUrl}/authorize?` + new URLSearchParams({
response_type: 'id_token token',
response_mode: 'form_post',
client_id: clientId,
redirect_uri: redirectUri,
scope: scopes,
state,
nonce,
audience: config.audience ?? `io.bridge`,

}).toString();

logger.info(`redirecting to Auth0: ${authorizeUrl}`);
response.setRawStatusCode(302);
response.headers.set('Location', authorizeUrl);
await response.end();
}
},
// ---------------------------------------------------------------
// POST /login/oauth2/code/auth0 — Auth0 posts tokens here
// ---------------------------------------------------------------
{
request: { method: 'POST', path: loginCallbackPath },
options: { authorize: { access: 'permitted' } },
handler: async ({ request, response }) => {
const formData = await request.formData();

const error = formData.get('error');
const errorDescription = formData.get('error_description');

if (error) {
logger.warn(`auth0 error: ${error} — ${errorDescription}`);
response.setRawStatusCode(400);
await response.body(new TextEncoder().encode(
`Auth0 login failed: ${error} — ${errorDescription ?? 'unknown error'}`
));
await response.end();
return;
}

const idToken = formData.get('id_token');
let user = 'dev-user';
let username;
if (idToken) {
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
user = payload.email;
username = payload.name;
}
username ??= user;

// set authInfo with the received tokens and user
config.authInfoResolver({
token: formData.get('access_token'),
user,
username
});

response.setRawStatusCode(200);
response.headers.set('Content-Type', 'text/html');


await response.body(new TextEncoder().encode(successPage.replace('${username}', `${username} (${user})`)));
await response.end();
}
}
);
}
Empty file added office/auth0/readme.md
Empty file.
Loading
Loading