Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .azure-pipelines-gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
pip install wheel
pip install -U -r doc/requirements.txt
pip install -U -e ./python
npm install typescript typedoc@0.19.2

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sphinx-js doesn't support newer versions yet.

NPM_BIN=$(pwd)/node_modules/.bin
export PATH="$NPM_BIN:$PATH"
sphinx-multiversion doc build/html
displayName: Sphinx

Expand Down
65 changes: 30 additions & 35 deletions doc/build_apps/js_app_bundle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,48 +112,43 @@ Globals
JavaScript provides a set of built-in
`global functions, objects, and values <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects>`_.

CCF provides the following additional globals:

- ``tables``:
Provides access to the Key-Value Store of the network.
``tables`` is an object that maps table names to ``Table`` objects.
A ``Table`` object has ``get(k)``/``put(k,v)``/``remove(k)`` functions.
Keys and values currently have to be strings, although this will change in the near future.

Example: ``tables['msg'].put('123', 'Hello world!')``
CCF provides the additional global variable ``ccf`` to access native CCF functionality.
It is an object implementing the :js:class:`CCF <CCF>` interface documented below.

.. note::
`Web APIs <https://developer.mozilla.org/en-US/docs/Web/API>`_ are not available.

.. js:autoclass:: CCF
:members:

.. js:autoclass:: KVMap
:members:

.. js:autoclass:: WrapAlgoParams
:members:

.. js:autoclass:: RsaOaepParams
:members:

.. js:autoclass:: AESKWPParams
:members:

.. js:autoclass:: RsaOaepAESKWPParams
:members:

.. js:autoclass:: CryptoKeyPair
:members:

Endpoint handlers
~~~~~~~~~~~~~~~~~

An endpoint handler is an exported function that receives a ``Request`` object, returns a ``Response`` object,
and is referenced in the ``app.json`` file of the app bundle (see above).

A ``Request`` object has the following fields:

- ``headers``: An object mapping lower-case HTTP header names to their values.
- ``params``: An object mapping URL path parameter names to their values.
- ``query``: The query string of the requested URL.
- ``body``: An object with ``text()``/``json()``/``arrayBuffer()`` functions to access the
request body in various ways.
- ``caller``: An object describing the authenticated identity retrieved by this endpoint's authentication policies.
``caller.policy`` is a string indicating which policy accepted this request, for use when multiple policies are
listed. The other fields depend on which policy accepted; most set ``caller.id``, ``caller.data``, and ``caller.cert``,
while the ``"jwt"`` policy sets ``caller.jwt``.

A ``Response`` object can contain the following fields (all optional):

- ``statusCode``: The HTTP status code to return (default ``200``, or ``500`` if an exception is raised).
- ``headers``: An object mapping lower-case HTTP header names to their values.
The type of ``body`` determines the default value of the ``content-type`` header, see below.
- ``body``: Either
a `string <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String>`_ (``text/plain``),
an `ArrayBuffer <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer>`_ (``application/octet-stream``),
a `TypedArray <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray>`_ (``application/octet-stream``),
or as fall-back any `JSON-serializable <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify>`_ value (``application/json``).
The content type in parentheses is the default and can be overridden in ``headers``.
An endpoint handler is an exported function that receives a :js:class:`Request <Request>` object, returns a :js:class:`Response <Response>` object, and is referenced in the ``app.json`` file of the app bundle (see above).

.. js:autoclass:: Request
:members:

.. js:autoclass:: Response
:members:

See the following handler from the example app bundle in the
`tests/js-app-bundle <https://github.com/microsoft/CCF/tree/main/tests/js-app-bundle>`_
Expand Down
14 changes: 8 additions & 6 deletions doc/build_apps/js_app_ts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ The sample app has the following folder layout:
│ │ ├── crypto.ts
│ │ ├── partition.ts
│ │ └── proto.ts
│ └── types
│ └── ccf.ts
│ └── ccf
│ ├── builtin.ts
│ └── util.ts
├── app.json
├── package.json
├── rollup.config.js
Expand All @@ -40,7 +41,8 @@ The sample app has the following folder layout:
It contains these files:

- ``src/endpoints/*.ts``: :ref:`build_apps/js_app_ts:Endpoint handlers`.
- ``src/types/ccf.ts``: :ref:`build_apps/js_app_ts:Type definitions` for CCF objects.
- ``src/ccf/builtin.ts``: :ref:`build_apps/js_app_ts:Type definitions` for CCF objects.
- ``src/ccf/util.ts``: Utilities for working with CCF's Key Value Store.
- ``app.json``: :ref:`App metadata <build_apps/js_app_ts:Metadata>`.
- ``package.json``: Dependencies and build command.
- ``rollup.config.js``: Rollup configuration, see :ref:`build_apps/js_app_ts:Conversion to an app bundle` for more details.
Expand Down Expand Up @@ -70,7 +72,7 @@ An endpoint handler, here named ``abc``, has the following structure:
...
}

export function abc(request: ccf.Request<AbcRequest>): ccf.Response<AbcResponse> {
export function abc(request: Request<AbcRequest>): Response<AbcResponse> {
// access request details
const data = request.body.json();

Expand All @@ -86,7 +88,7 @@ An endpoint handler, here named ``abc``, has the following structure:
}

``AbcRequest`` and ``AbcResponse`` define the JSON schema of the request and response body, respectively.
If an endpoint has no request or response body, the type parameters of ``ccf.Request``/``ccf.Response`` can be omitted.
If an endpoint has no request or response body, the type parameters of :js:class:`Request`/:js:class:`Response` can be omitted.

As an example, the ``/partition`` endpoint of the sample app is implemented as:

Expand All @@ -111,7 +113,7 @@ CCF currently does not provide an npm package with TypeScript definitions
for :ref:`CCF's JavaScript API <build_apps/js_app_bundle:JavaScript API>`.

Instead, the definitions are part of the sample app in
`src/types/ccf.ts <https://github.com/microsoft/CCF/tree/main/tests/npm-app/src/types/ccf.ts>`_.
`src/ccf/builtin.ts <https://github.com/microsoft/CCF/tree/main/tests/npm-app/src/ccf/builtin.ts>`_.
See `src/endpoints <https://github.com/microsoft/CCF/tree/main/tests/npm-app/src/endpoints>`_
on how the types can be imported and used.

Expand Down
18 changes: 10 additions & 8 deletions doc/build_apps/js_app_tsoa.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ The sample app has the following folder layout:
│ │ └── site.ts
│ ├── models
│ │ └── poll.ts
│ ├── types
│ │ └── ccf.ts
│ ├── ccf
│ │ ├── builtin.ts
│ │ └── util.ts
│ ├── authentication.ts
│ └── error_handler.ts
├── tsoa-support
Expand All @@ -59,7 +60,8 @@ It contains these files:

- ``src/controllers/*.ts``: :ref:`build_apps/js_app_tsoa:Controllers`.
- ``src/models/*.ts``: Data models shared between endpoint handlers.
- ``src/types/ccf.ts``: :ref:`build_apps/js_app_tsoa:Type definitions` for CCF objects.
- ``src/ccf/builtin.ts``: :ref:`build_apps/js_app_tsoa:Type definitions` for CCF objects.
- ``src/ccf/util.ts``: Utilities for working with CCF's Key Value Store.
- ``src/authentication.ts``: `authentication module <https://tsoa-community.github.io/docs/authentication.html>`_.
See also :ref:`build_apps/auth/jwt_ms_example:JWT Authentication example using Microsoft Identity Platform`.
- ``src/error_handler.ts``: global error handler.
Expand Down Expand Up @@ -110,18 +112,18 @@ CCF currently does not provide an npm package with TypeScript definitions
for :ref:`CCF's JavaScript API <build_apps/js_app_bundle:JavaScript API>`.

Instead, the definitions are part of the sample app in
`src/types/ccf.ts <https://github.com/microsoft/CCF/tree/samples/apps/forum/src/types/ccf.ts>`_.
`src/ccf/builtin.ts <https://github.com/microsoft/CCF/tree/samples/apps/forum/src/ccf/builtin.ts>`_.

Using CCF's ``Response`` object is not needed when using tsoa because the return value always has to be the body itself.
Using CCF's :js:class:`Response` object is not needed when using tsoa because the return value always has to be the body itself.
Headers and the status code can be set using `Controller methods <https://tsoa-community.github.io/reference/classes/_tsoa_runtime.controller-1.html>`_.

Sometimes though it is necessary to access CCF's ``Request`` object, for example when the request body is not JSON.
In this case, instead of using ``@Body() body: MyType`` as function argument, ``@Request() request: ccf.Request`` can be used.
Sometimes though it is necessary to access CCF's :js:class:`Request` object, for example when the request body is not JSON.
In this case, instead of using ``@Body() body: MyType`` as function argument, ``@Request() request: CCF.Request`` can be used.
See `src/controllers/csv.ts <https://github.com/microsoft/CCF/tree/main/samples/apps/forum/src/controllers/csv.ts>`_
for a concrete example.

.. warning::
Requesting CCF's ``Request`` object via ``@Request()`` instead of using ``@Body()`` disables automatic schema validation.
Requesting CCF's :js:class:`Request` object via ``@Request()`` instead of using ``@Body()`` disables automatic schema validation.

Metadata
--------
Expand Down
7 changes: 6 additions & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"sphinx_copybutton",
"sphinx.ext.autodoc",
"sphinxcontrib.openapi",
"sphinx_panels"
"sphinx_panels",
"sphinx_js"
]

autosectionlabel_prefix_document = True
Expand Down Expand Up @@ -227,6 +228,10 @@
tokenizer_lang = "en_UK"
spelling_word_list_filename = ["spelling_wordlist.txt"]

# sphinx_js options
js_language = 'typescript'
js_source_path = '../src/js'
jsdoc_config_path = '../tests/npm-app/tsconfig.json'

def setup(self):
import subprocess
Expand Down
1 change: 1 addition & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ pydata-sphinx-theme
sphinx-copybutton
sphinxcontrib.openapi
sphinx-panels
sphinx-js
3 changes: 3 additions & 0 deletions livehtml.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ fi
source env/bin/activate
pip install --disable-pip-version-check -q -U -e ./python/
pip install --disable-pip-version-check -q -U -r ./doc/requirements.txt
npm install typescript typedoc@0.19.2
NPM_BIN=$(pwd)/node_modules/.bin
export PATH="$NPM_BIN:$PATH"
echo "Python environment successfully setup"

sphinx-autobuild -b html doc doc/html --host localhost --port 8080
19 changes: 10 additions & 9 deletions samples/apps/forum/src/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import { KJUR, KEYUTIL } from "jsrsasign";
import jwt_decode from "jwt-decode";
import { Base64 } from "js-base64";
import * as ccf from "./types/ccf";
import { ccf, Request } from "./ccf/builtin";
import * as ccfUtil from "./ccf/util";
import { UnauthorizedError } from "./error_handler";

export interface User {
Expand Down Expand Up @@ -33,7 +34,7 @@ export const MS_APP_ID = "1773214f-72b8-48f9-ae18-81e30fab04db";
export const MS_APP_ID_URI = "api://1773214f-72b8-48f9-ae18-81e30fab04db";

export function authentication(
request: ccf.Request,
request: Request,
securityName: string,
scopes?: string[]
): void {
Expand Down Expand Up @@ -65,10 +66,10 @@ export function authentication(
}

// Get the stored signing key to validate the token.
const keysMap = new ccf.TypedKVMap(
const keysMap = new ccfUtil.TypedKVMap(
ccf.kv["public:ccf.gov.jwt.public_signing_keys"],
ccf.string,
ccf.typedArray(Uint8Array)
ccfUtil.string,
ccfUtil.typedArray(Uint8Array)
);
const publicKeyDer = keysMap.get(signingKeyId);
if (publicKeyDer === undefined) {
Expand Down Expand Up @@ -98,10 +99,10 @@ export function authentication(
}

// Get the issuer associated to the signing key.
const keyIssuerMap = new ccf.TypedKVMap(
const keyIssuerMap = new ccfUtil.TypedKVMap(
ccf.kv["public:ccf.gov.jwt.public_signing_key_issuer"],
ccf.string,
ccf.string
ccfUtil.string,
ccfUtil.string
);
const keyIssuer = keyIssuerMap.get(signingKeyId);

Expand Down Expand Up @@ -136,7 +137,7 @@ export function authentication(
throw new Error(`BUG: unknown key issuer: ${keyIssuer}`);
}

request.user = {
request.caller = {
claims: claims,
userId: claims.sub,
} as User;
Expand Down
1 change: 1 addition & 0 deletions samples/apps/forum/src/ccf
10 changes: 5 additions & 5 deletions samples/apps/forum/src/controllers/csv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
UnauthorizedError,
} from "../error_handler";
import { User } from "../authentication";
import * as ccf from "../types/ccf";
import * as CCF from "../ccf/builtin";
import { kv } from "../models/poll";

// GET /csv return all opinions of authenticated user as CSV
Expand All @@ -46,8 +46,8 @@ export class CSVController extends Controller {

@SuccessResponse(200, "Opinions of authenticated user in CSV format")
@Get()
public getOpinionsAsCSV(@Request() request: ccf.Request): any {
const user: User = request.user;
public getOpinionsAsCSV(@Request() request: CCF.Request): any {
const user: User = request.caller;

const rows = [];
this.kvPolls.forEach((poll, topic) => {
Expand All @@ -69,8 +69,8 @@ export class CSVController extends Controller {
"Opinions were not recorded because either an opinion data type did not match the poll type or a poll with the given topic was not found"
)
@Post()
public submitOpinionsFromCSV(@Request() request: ccf.Request): void {
const user: User = request.user;
public submitOpinionsFromCSV(@Request() request: CCF.Request): void {
const user: User = request.caller;

const rows = parse<any>(request.body.text(), { header: true }).data;

Expand Down
26 changes: 13 additions & 13 deletions samples/apps/forum/src/controllers/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
UnauthorizedError,
} from "../error_handler";
import { User } from "../authentication";
import * as ccf from "../types/ccf";
import * as CCF from "../ccf/builtin";
import { kv } from "../models/poll";

export const MINIMUM_OPINION_THRESHOLD = 10;
Expand Down Expand Up @@ -116,9 +116,9 @@ export class PollController extends Controller {
public createPoll(
@Path() topic: string,
@Body() body: CreatePollRequest,
@Request() request: ccf.Request
@Request() request: CCF.Request
): void {
const user: User = request.user;
const user: User = request.caller;

if (this.kvPolls.has(topic)) {
throw new ForbiddenError("Poll with given topic exists already");
Expand All @@ -139,9 +139,9 @@ export class PollController extends Controller {
@Post()
public createPolls(
@Body() body: CreatePollsRequest,
@Request() request: ccf.Request
@Request() request: CCF.Request
): void {
const user: User = request.user;
const user: User = request.caller;

for (let [topic, poll] of Object.entries(body.polls)) {
if (this.kvPolls.has(topic)) {
Expand Down Expand Up @@ -169,9 +169,9 @@ export class PollController extends Controller {
public submitOpinion(
@Path() topic: string,
@Body() body: SubmitOpinionRequest,
@Request() request: ccf.Request
@Request() request: CCF.Request
): void {
const user: User = request.user;
const user: User = request.caller;

const poll = this.kvPolls.get(topic);
if (poll === undefined) {
Expand All @@ -193,9 +193,9 @@ export class PollController extends Controller {
@Put()
public submitOpinions(
@Body() body: SubmitOpinionsRequest,
@Request() request: ccf.Request
@Request() request: CCF.Request
): void {
const user: User = request.user;
const user: User = request.caller;

for (const [topic, opinion] of Object.entries(body.opinions)) {
const poll = this.kvPolls.get(topic);
Expand All @@ -222,9 +222,9 @@ export class PollController extends Controller {
@Get("{topic}")
public getPoll(
@Path() topic: string,
@Request() request: ccf.Request
@Request() request: CCF.Request
): GetPollResponse {
const user: User = request.user;
const user: User = request.caller;

if (!this.kvPolls.has(topic)) {
throw new NotFoundError("Poll does not exist");
Expand All @@ -236,8 +236,8 @@ export class PollController extends Controller {

@SuccessResponse(200, "Poll data")
@Get()
public getPolls(@Request() request: ccf.Request): GetPollsResponse {
const user: User = request.user;
public getPolls(@Request() request: CCF.Request): GetPollsResponse {
const user: User = request.caller;

let response: GetPollsResponse = { polls: {} };

Expand Down
Loading