Skip to content

Commit

Permalink
feat: access bridge service (#616)
Browse files Browse the repository at this point in the history
* feat(project): access bridge initial structure

* feat(project): yarn

* feat(project): add lint-staged

* feat(project): error for unused imports

* feat(project): remove not needed ls-lint

* feat(project): add access tests

* feat(project): rename error message

* feat(project): readme and docs

* chore: rename project in package

* feat(project): include SIMS authorization

* chore: replace jw/fet with standard prettier config

* chore: access-bridge changes to not trigger e2e tests

* chore: revert web env

* feat(project): add stripe products endpoint

* chore: yarn update

* feat(project): move types in common package

* feat(project): update allowed methods

* chore: rename stripe service fnc name

* feat(project): make authorization optional

* feat(project): redefine functions with obj params

* chore: consistent naming convention

* feat(project): add test fixtures

* feat(project): add tests for stripe

* feat(project): fine-tune error descriptions

* feat(project): add stripe checkout

* chore: move types in common

* chore: move types in common

* feat(project): add tests for stripe checkout

* feat(project): add more tests for checkout

* chore: stripe checkout params

* feat(project): add test mocks class

* feat(project): add metadata in stripe checkout

* feat(project): add viewer from auth token, handle plan external providers

* feat(project): add viewer from auth token, handle plan external providers

* chore: update readme

* feat(project): update docs

* feat(project): add account service

* chore: remove unused util fnc

* feat(project): add sentry

* feat(project): replace node with vite

* feat(project): refactor server to use express, refactor error handling as well as tests

* feat(project): add types for express

* chore: add .test in the test naimings

* fix: vite version mismatch

* feat(project): update docs

* chore: add dummy env variables in the test-unit-snapshot workflow

* feat(project): refactor services, decouple plans method

* feat(project): add sentry source mapping

* feat(project): add sentry version and environment

* chore: remove debug sentry

* feat(project): additional test build for not source mapping on test

* feat(project): add stripe billing portal endpoint

* chore: update readme

* chore: add stripe dummy secret on test-unit workflow

* chore: update checkout params

* feat(project): add cors middleware

* feat(project): add cors middleware

* feat(project): add site_id in the route

* feat(project): update port it can match google cloud run reqs

* feat(project): use newer version of node to make happy sentry

* feat(project): move error handling to the middleware

* feat(project): add refresh passport

* feat(project): simplify jw error usage

* feat(project): refactor routes and middlewares

* feat(project): refactor structure and namings

* feat(project): add missing case for refresh passport test

* feat(project): load envs at runtime

* fix(search): override search query cache (#594)

Co-authored-by: Mike van Veenhuijzen <mike@videodock.com>

* feat(project): app metadata insertion

* chore(project): add apple smart banner tag via env var
* chore(project): add android native banner data via env var
* refactor(project): make code more readable
* refactor(project): add itunes related application metadata
* refactor(project): function rename

* feat(project): add injectable wrapper to common components (#598)

* refactor(project): update services members visibility and explicit inject (#590)

* feat(project): remove free and productIds from content-types.json (#605)

* fix(e2e): fix tests after cleeng api update (#606)

* chore(release): v6.6.0

* feat(project): refactor routes and middlewares

* feat(project): rebase and stuff

* feat(project): add generic payment service that other services will implement

* feat(project): update products to use our own types

* chore: add site_id in .env.example

* feat(project): add stripe error handler in middleware

* chore: update readme

* chore: update comments on stripe payment service

* chore: remove unused type

* feat(project): update sentry and logger

* chore: node-version

* chore: revert node version

* chore: update comment on checkout controller

* chore: revert node version

* chore: revert env to default

* feat(project): modify types for products and prices

* feat(project): add deployment file and docs

* chore: update readme paths

* feat(project): remove mode from checkout params

* feat(project): remove stripe type

* chore: remove vite build file

---------

Co-authored-by: langemike <mikevv@gmail.com>
Co-authored-by: Mike van Veenhuijzen <mike@videodock.com>
Co-authored-by: Roy Schut <roy@videodock.com>
Co-authored-by: Christiaan Scheermeijer <christiaan@videodock.com>
Co-authored-by: Carina Dragan <92930790+CarinaDraganJW@users.noreply.github.com>
Co-authored-by: Anton Lantukh <alantukh@jwplayer.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
  • Loading branch information
8 people authored Sep 23, 2024
1 parent c27a1e6 commit aae2551
Show file tree
Hide file tree
Showing 53 changed files with 4,402 additions and 24 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/test-unit-snapshot.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test - Unit and Snapshot
name: Test - Unit, Integration, Snapshot

on:
push:
Expand All @@ -21,3 +21,11 @@ jobs:
yarn test
env:
CI: true
# Dummy environment variables for testing the access-bridge service
APP_SITE_ID: test1234
APP_API_SECRET: dummy_secret
APP_STRIPE_SECRET: dummy_stripe_secret
APP_BIND_ADDR: localhost
APP_BIND_PORT: 8080
APP_ACCESS_CONTROL_API_HOST: https://test-cdn.jwplayer.com
APP_SIMS_API_HOST: https://test-sims.jwplayer.com
1 change: 0 additions & 1 deletion knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const config: KnipConfig = {
'i18next-parser',
'luxon', // Used in tests
'playwright', // Used in test configs
'sharp', // Requirement for @vite-pwa/assets-generator
'tsconfig-paths', // Used for e2e test setup
'virtual:pwa-register', // Service Worker code is injected at build time
'virtual:polyfills', // Polyfills are conditionally injected
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"prepare": "husky install",
"test": "TZ=UTC LC_ALL=en_US.UTF-8 vitest run",
"test-watch": "TZ=UTC LC_ALL=en_US.UTF-8 vitest",
"web": "yarn --cwd platforms/web"
"web": "yarn --cwd platforms/web",
"access-bridge": "yarn --cwd platforms/access-bridge"
},
"devDependencies": {
"@commitlint/cli": "^17.8.1",
Expand Down
8 changes: 8 additions & 0 deletions packages/common/types/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type JWError = {
code: string;
description: string;
};

export type JWErrorResponse = {
errors: JWError[];
};
4 changes: 4 additions & 0 deletions packages/common/types/passport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type PassportResponse = {
passport: string;
refresh_token: string;
};
49 changes: 49 additions & 0 deletions packages/common/types/payment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
type Recurrence = {
// The frequency at which a subscription is billed. One of `day`, `week`, `month`, or `year`.
interval: 'day' | 'week' | 'month' | 'year';
// Recurrence duration, for example, `interval=month` and `duration=3` bills every 3 months.
duration: number;
// Trial period interval. For example, a month-long trial is different from a 30-day trial.
trial_period_interval: 'day' | 'week' | 'month' | 'year';
// Duration of the trial period (in the unit defined by trial_period_interval).
trial_period_duration: number | null;
};

export type Price = {
// Unique identifier for the object.
store_price_id: string;
// Dictionary of currencies, where the key is the currency code and the value is an object with an amount property.
// Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase.
currencies: {
[currency_code: string]: {
// The unit amount in cents (or local equivalent) to be charged, represented as a whole integer.
amount: number | null;
};
};
// Default currency code for this price.
default_currency: string;
// Recurrence details. Can be a Recurrence object or 'one_time'.
recurrence: Recurrence | 'one_time';
// Billing scheme. For now, we only support `per_unit`.
billing_scheme: 'per_unit';
};

export type Product = {
// Unique identifier for the object.
store_product_id: string;
// The product's name, meant to be displayable to the customer.
name: string;
// The product's description, meant to be displayable to the customer.
description: string;
// The ID of the default price this product is associated with.
default_store_price_id: string;
// Array of price objects.
prices: Price[];
};

// General checkout parameters type. Can be extended by specific payment providers, e.g. Stripe
export type CheckoutParams = {
price_id: string;
success_url: string;
cancel_url: string;
};
31 changes: 31 additions & 0 deletions packages/common/types/plans.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
type AccessOptions = {
drm_policy_id: string;
};

type PlanExternalProviders = {
stripe?: string;
apple?: string;
google?: string;
};

export type AccessControlPlan = {
id: string;
exp: number;
};

export type Plan = {
id: string;
exp: number;
access_model: 'free' | 'freeauth' | 'svod';
access: AccessOptions;
metadata: {
external_providers: PlanExternalProviders;
};
};

export type PlansResponse = {
total: number;
page: number;
page_length: number;
plans: Plan[];
};
37 changes: 37 additions & 0 deletions platforms/access-bridge/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Application version - Used for tracking and identifying specific releases.
# Automatically set to the version specified in package.json.
# Helps in associating logs, errors, and other metrics with particular versions of the application.
APP_VERSION=$npm_package_version

# Secrets responsible for signing a request to authenticate with the JW Delivery Gateway.
# In order to use the AC System this authentication is crucial.
# The secrets can be found in the JW Dashboard, under the API Credentials in the top right settings icon.
# Make sure the secrets are V1 and that they refer to the desired property.
# For production env, use this reference on how to store them: https://cloud.google.com/run/docs/configuring/services/secrets
APP_API_SECRET=customer_v1_secret
# site_id or property_id represents the key that corresponds to the APP_API_SECRET defined earlier.
APP_SITE_ID=customer_site_id
# Stripe secret responsible for authenticating Stripe API calls
APP_STRIPE_SECRET=stripe_secret

# Non-secret variables
# Specifies the network address or IP address on which the server listens for incoming connections.
APP_BIND_ADDR=localhost
# Specifies the port number on which the server listens for incoming connections.
APP_BIND_PORT=8080
# Specifies the client URL responsible for access related stuff
APP_ACCESS_CONTROL_API_HOST=https://cdn-dev.jwplayer.com
# Specifies the client URL responsible for plans related stuff
APP_SIMS_API_HOST=https://daily-sims.jwplayer.com

# These are optional and should be added only if tracing with Sentry is needed
# Set the APP_SENTRY_DSN variable to enable Sentry error tracking and performance monitoring.
# Set APP_SENTRY_AUTH_TOKEN to allow Sentry to provide readable stack traces (source maps).
# If this variable is not set, Sentry will not be initialized.
# For production environments, ensure you configure the `APP_SENTRY_TRACE_RATE`
# according to your monitoring needs to balance performance and resource usage.
APP_SENTRY_DSN=
APP_SENTRY_AUTH_TOKEN=
APP_SENTRY_TRACE_RATE=
APP_SENTRY_ORG_NAME=
APP_SENTRY_PROJ_NAME=
12 changes: 12 additions & 0 deletions platforms/access-bridge/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
extends: ['jwp/typescript'],
rules: {
"max-len": ["error", { "code": 120 }],
"import/no-unused-modules": ["error"]
},
env: {
node: true, // Enables recognition of Node.js global variables and scoping rules
},
ignorePatterns: ['build'],
};

6 changes: 6 additions & 0 deletions platforms/access-bridge/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
build
*.local
.env
# Sentry Config File
.env.sentry-build-plugin
1 change: 1 addition & 0 deletions platforms/access-bridge/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
70 changes: 70 additions & 0 deletions platforms/access-bridge/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
PACKAGE_JSON := ./package.json
BACKUP_PACKAGE_JSON := ./package-backup.json
ROOT_YARN_LOCK := ../../yarn.lock
PROJECT_YARN_LOCK := ./yarn.lock

# Default target: deploy and ensure cleanup runs regardless
.PHONY: deploy
deploy: check-setup backup-package modify-package copy-yarn-lock try-deploy post-deploy

# Step 0: Check setup
.PHONY: check-setup
check-setup:
@echo "Checking setup..."
@command -v gcloud >/dev/null 2>&1 || { echo "Error: gcloud is not installed. Please install Google Cloud SDK."; exit 1; }
@command -v yarn >/dev/null 2>&1 || { echo "Error: yarn is not installed. Please install Yarn."; exit 1; }
@echo "All required tools are installed."
@# Check if Google Cloud is configured
@gcloud config get-value project >/dev/null 2>&1 || { echo "Error: Google Cloud project is not configured. Run 'gcloud init'."; exit 1; }
@echo "Google Cloud is configured."

# Step 1: Backup the original package.json
.PHONY: backup-package
backup-package:
@echo "Deployment started..."
@cp $(PACKAGE_JSON) $(BACKUP_PACKAGE_JSON)

# Step 2: Modify `package.json` by removing internal dependencies
# In a monorepo setup, internal packages are not deployed. This step ensures that only the relevant,
# external dependencies are included in the `package.json` for deployment.
.PHONY: modify-package
modify-package:
@# Use `sed` to remove specific internal dependencies by editing package.json in place
@sed -i.bak '/"@jwp\/ott-common"/d' $(PACKAGE_JSON)
@sed -i.bak '/"eslint-config-jwp"/d' $(PACKAGE_JSON)
@rm -f $(PACKAGE_JSON).bak

# Step 3: Copy the root `yarn.lock` to the project directory
# To maintain dependency consistency in a monorepo, copy the root `yarn.lock` file to the project
# directory so Google Cloud Run can correctly compare and resolve dependencies.
.PHONY: copy-yarn-lock
copy-yarn-lock:
@cp $(ROOT_YARN_LOCK) $(PROJECT_YARN_LOCK)

# Step 4: Deploy to Google Cloud Run
.PHONY: try-deploy
try-deploy:
@{ \
set -e; \
trap 'make post-deploy; exit 1;' INT TERM HUP; \
gcloud run deploy access-bridge \
--source=. \
--platform=managed \
--region= \
--allow-unauthenticated; \
make post-deploy; \
}

# Step 5: Cleanup (run whether deploy succeeds or fails)
.PHONY: post-deploy
post-deploy: restore-package clean

# Step 6: Restore the original package.json
.PHONY: restore-package
restore-package:
@mv $(BACKUP_PACKAGE_JSON) $(PACKAGE_JSON)

# Step 7: Clean up copied yarn.lock
.PHONY: clean
clean:
@rm -f $(PROJECT_YARN_LOCK)
134 changes: 134 additions & 0 deletions platforms/access-bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Access Bridge

A service that facilitates seamless communication between the Subscriber Identity Management System (SIMS) and Access Control services. It provides endpoints to generate access passports for authenticated viewers, ensuring secure and efficient access management.

## Local Setup for Environment Variables

To set up the project locally, you need to configure environment variables that are crucial for authenticating with the JW Delivery Gateway and for specifying server configurations.

Here’s how you can set them up:

Create a `.env.local` file in the root of this project and add the following variables:

- APP_API_SITE_ID=customer_site_id
- APP_API_SECRET=customer_v1_secret
- APP_BIND_ADDR=localhost
- APP_BIND_PORT=8080
- APP_ACCESS_CONTROL_API_HOST=https://cdn-dev.jwplayer.com
<em>(Use https://cdn.jwplayer.com for production)</em>
- APP_SIMS_API_HOST=https://daily-sims.jwplayer.com
<em>(Use https://sims.jwplayer.com for production)</em>

Make sure to replace the placeholder values (e.g., customer_v1_secret) with the actual values from your JW Dashboard.
You can also copy and paste the contents of `.env.example` into `.env.local` and just adjust the APP_API_SECRET.

## Getting started

- Run `yarn` to install dependencies
- Navigate to the platform directory `cd platforms/access-bridge`
- Run tests through `yarn test`
- Run `yarn start` to start the server

## Exposed endpoints

#### URL: `/v2/sites/{site_id}/access/generate`

- **Method:** PUT
- **Authorization:** Valid SIMS token
- **Summary:** Generates a new passport for an authenticated viewer based on the information inside the SIMS token.
- **Response:**
```json
{
"passport": "encrypted_passport",
"refresh_token": "random_string"
}
```

#### URL: `/v2/sites/{site_id}/access/refresh`

- **Method:** PUT
- **Authorization:** Valid SIMS token
- **Summary:** Regenerates an existing passport with a new expiry and a new refresh token.
- **Request:**
```json
{
"refresh_token": "string"
}
```
- **Response:**
```json
{
"passport": "encrypted_passport",
"refresh_token": "random_string"
}
```

#### URL: `/v2/sites/{site_id}/products`

- **Method:** GET
- **Authorization:** None
- **Summary:** Lists all the corresponding stripe products with prices that are connected to the SIMS plans.
- **Response:** [Product payment type](../../../ott-web-app/packages/common/types/payment.ts)
```json
[
{
// ...
"id": "prod_QRUHbH7wK5HHPr",
"default_price": "price_1PabInA9TD3ZjIM6EEnKSR7U",
// ...
"prices": [
{
// ...
"id": "price_1PabInA9TD3ZjIM6EEnKSR7U",
"currency": "usd",
"unit_amount": 15000
// ...
}
]
}
// ...
]
```

#### URL: `/v2/sites/{site_id}/checkout`

- **Method:** POST
- **Authorization:** Valid SIMS token
- **Summary:** Creates Payment Checkout Session URL where the viewer will be redirected to complete the payment.
- **Request:**
```json
{
"price_id": "string", // id of the price that is about to be paid
"mode": "string", // subscription (recurring) | payment (one time purchases)
"success_url": "string", // redirect after successful payment
"cancel_url": "string" // redirect after cancel / invalid payment
}
```
- **Response:**
```json
{
"url": "string" // url where the viewer will be redirected to complete the payment.
}
```

#### URL: `/v2/sites/{site_id}/billing-portal`

- **Method:** POST
- **Authorization:** Valid SIMS token
- **Summary:** Generates Billing Portal Url, where the viewer can view / update their purchase info.
- **Response:**
```json
{
"url": "billing-portal-url"
}
```

## Developer guidelines

- Read the workspace guidelines here [developer-guidelines.md](./docs/developer-guidelines.md).
- Read the deployment guidelines here [deployment.md](./docs/deployment.md).
- Read the web platform guidelines here [developer-guidelines.md](../../docs/developer-guidelines.md).

```

```
Loading

0 comments on commit aae2551

Please sign in to comment.