Adding OpenAPI documentation for the pusher.
The pusher now exposes a "/openapi" endpoint and a "/swagger-ui/" endpoint.
This commit is contained in:
@@ -7,8 +7,10 @@ import { DebugController } from "./Controller/DebugController";
|
||||
import { AdminController } from "./Controller/AdminController";
|
||||
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
||||
import { WokaListController } from "./Controller/WokaListController";
|
||||
import { SwaggerController } from "./Controller/SwaggerController";
|
||||
import HyperExpress from "hyper-express";
|
||||
import { cors } from "./Middleware/Cors";
|
||||
import { ENABLE_OPENAPI_ENDPOINT } from "./Enum/EnvironmentVariable";
|
||||
|
||||
class App {
|
||||
public app: HyperExpress.compressors.TemplatedApp;
|
||||
@@ -31,6 +33,9 @@ class App {
|
||||
new AdminController(webserver);
|
||||
new OpenIdProfileController(webserver);
|
||||
new WokaListController(webserver);
|
||||
if (ENABLE_OPENAPI_ENDPOINT) {
|
||||
new SwaggerController(webserver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,27 @@ export class AdminController extends BaseHttpController {
|
||||
this.receiveRoomEditionPrompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /room/refresh:
|
||||
* post:
|
||||
* description: Forces anyone out of the room. The request must be authenticated with the "admin-token" header.
|
||||
* parameters:
|
||||
* - name: "admin-token"
|
||||
* in: "header"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* description: TODO - move this to a classic "Authorization" header!
|
||||
* - name: "roomId"
|
||||
* in: "body"
|
||||
* description: "The ID (full URL) to the room"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Will always return "ok".
|
||||
* example: "ok"
|
||||
*/
|
||||
receiveRoomEditionPrompt() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.app.post("/room/refresh", { middlewares: [adminToken] }, async (req, res) => {
|
||||
@@ -43,6 +64,40 @@ export class AdminController extends BaseHttpController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /message:
|
||||
* post:
|
||||
* description: Sends a message (or a world full message) to a number of rooms.
|
||||
* parameters:
|
||||
* - name: "admin-token"
|
||||
* in: "header"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* description: TODO - move this to a classic "Authorization" header!
|
||||
* - name: "text"
|
||||
* in: "body"
|
||||
* description: "The text of the message"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "type"
|
||||
* in: "body"
|
||||
* description: Either "capacity" or "message
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "targets"
|
||||
* in: "body"
|
||||
* description: The list of room IDs to target
|
||||
* required: true
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* example: "https://play.workadventu.re/@/foo/bar/baz"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Will always return "ok".
|
||||
* example: "ok"
|
||||
*/
|
||||
receiveGlobalMessagePrompt() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.app.post("/message", { middlewares: [adminToken] }, async (req, res) => {
|
||||
|
||||
@@ -21,6 +21,37 @@ export class AuthenticateController extends BaseHttpController {
|
||||
}
|
||||
|
||||
openIDLogin() {
|
||||
/**
|
||||
* @openapi
|
||||
* /login-screen:
|
||||
* get:
|
||||
* description: Redirects the user to the OpenID login screen
|
||||
* parameters:
|
||||
* - name: "nonce"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "state"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "playUri"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: false
|
||||
* type: "string"
|
||||
* - name: "redirect"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: false
|
||||
* type: "string"
|
||||
* responses:
|
||||
* 302:
|
||||
* description: Redirects the user to the OpenID login screen
|
||||
*
|
||||
*/
|
||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.app.get("/login-screen", async (req, res) => {
|
||||
try {
|
||||
@@ -47,6 +78,37 @@ export class AuthenticateController extends BaseHttpController {
|
||||
}
|
||||
|
||||
openIDCallback() {
|
||||
/**
|
||||
* @openapi
|
||||
* /login-callback:
|
||||
* get:
|
||||
* description: TODO
|
||||
* parameters:
|
||||
* - name: "nonce"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "state"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "playUri"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: false
|
||||
* type: "string"
|
||||
* - name: "redirect"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: false
|
||||
* type: "string"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TODO
|
||||
*
|
||||
*/
|
||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.app.get("/login-callback", async (req, res) => {
|
||||
const IPAddress = req.header("x-forwarded-for");
|
||||
@@ -112,6 +174,22 @@ export class AuthenticateController extends BaseHttpController {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /logout-callback:
|
||||
* get:
|
||||
* description: TODO
|
||||
* parameters:
|
||||
* - name: "token"
|
||||
* in: "query"
|
||||
* description: "todo"
|
||||
* required: false
|
||||
* type: "string"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TODO
|
||||
*
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.app.get("/logout-callback", async (req, res) => {
|
||||
const { token } = parse(req.path_query);
|
||||
@@ -130,7 +208,56 @@ export class AuthenticateController extends BaseHttpController {
|
||||
});
|
||||
}
|
||||
|
||||
//Try to login with an admin token
|
||||
/**
|
||||
* @openapi
|
||||
* /register:
|
||||
* post:
|
||||
* description: Try to login with an admin token
|
||||
* parameters:
|
||||
* - name: "organizationMemberToken"
|
||||
* in: "body"
|
||||
* description: "A token allowing a user to connect to a given world"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The details of the logged user
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* authToken:
|
||||
* type: string
|
||||
* description: A unique identification JWT token
|
||||
* userUuid:
|
||||
* type: string
|
||||
* description: Unique user ID
|
||||
* email:
|
||||
* type: string|null
|
||||
* description: The email of the user
|
||||
* example: john.doe@example.com
|
||||
* roomUrl:
|
||||
* type: string
|
||||
* description: The room URL to connect to
|
||||
* example: https://play.workadventu.re/@/foo/bar/baz
|
||||
* organizationMemberToken:
|
||||
* type: string|null
|
||||
* description: TODO- unclear. It seems to be sent back from the request?
|
||||
* example: ???
|
||||
* mapUrlStart:
|
||||
* type: string
|
||||
* description: TODO- unclear. I cannot find any use of this
|
||||
* example: ???
|
||||
* textures:
|
||||
* type: string
|
||||
* description: TODO - document this is still needed
|
||||
* example: ???
|
||||
* messages:
|
||||
* type: array
|
||||
* description: The list of messages to be displayed when the user logs?
|
||||
* example: ???
|
||||
*/
|
||||
private register() {
|
||||
this.app.post("/register", (req, res) => {
|
||||
(async () => {
|
||||
@@ -166,7 +293,28 @@ export class AuthenticateController extends BaseHttpController {
|
||||
});
|
||||
}
|
||||
|
||||
//permit to login on application. Return token to connect on Websocket IO.
|
||||
/**
|
||||
* @openapi
|
||||
* /anonymLogin:
|
||||
* post:
|
||||
* description: Generates an "anonymous" JWT token allowing to connect to WorkAdventure anonymously.
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The details of the logged user
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* authToken:
|
||||
* type: string
|
||||
* description: A unique identification JWT token
|
||||
* userUuid:
|
||||
* type: string
|
||||
* description: Unique user ID
|
||||
* 403:
|
||||
* description: Anonymous login is disabled at the configuration level (environment variable DISABLE_ANONYMOUS = true)
|
||||
*/
|
||||
private anonymLogin() {
|
||||
this.app.post("/anonymLogin", (req, res) => {
|
||||
if (DISABLE_ANONYMOUS) {
|
||||
@@ -183,6 +331,21 @@ export class AuthenticateController extends BaseHttpController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /profile-callback:
|
||||
* get:
|
||||
* description: ???
|
||||
* parameters:
|
||||
* - name: "token"
|
||||
* in: "query"
|
||||
* description: "A JWT authentication token ???"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* responses:
|
||||
* 302:
|
||||
* description: Redirects the user to the profile screen of the admin
|
||||
*/
|
||||
profileCallback() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.app.get("/profile-callback", async (req, res) => {
|
||||
|
||||
@@ -10,6 +10,94 @@ import { BaseHttpController } from "./BaseHttpController";
|
||||
export class MapController extends BaseHttpController {
|
||||
// Returns a map mapping map name to file name of the map
|
||||
routes() {
|
||||
/**
|
||||
* @openapi
|
||||
* /map:
|
||||
* get:
|
||||
* description: Returns a map mapping map name to file name of the map
|
||||
* produces:
|
||||
* - "application/json"
|
||||
* parameters:
|
||||
* - name: "playUri"
|
||||
* in: "query"
|
||||
* description: "The full URL of WorkAdventure to load this map"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "authToken"
|
||||
* in: "query"
|
||||
* description: "The authentication token"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The details of the map
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - mapUrl
|
||||
* - policy_type
|
||||
* - tags
|
||||
* - textures
|
||||
* - authenticationMandatory
|
||||
* - roomSlug
|
||||
* - contactPage
|
||||
* - group
|
||||
* properties:
|
||||
* mapUrl:
|
||||
* type: string
|
||||
* description: The full URL to the JSON map file
|
||||
* example: https://myuser.github.io/myrepo/map.json
|
||||
* policy_type:
|
||||
* type: integer
|
||||
* description: ANONYMOUS_POLICY = 1, MEMBERS_ONLY_POLICY = 2, USE_TAGS_POLICY= 3
|
||||
* example: 1
|
||||
* tags:
|
||||
* type: array
|
||||
* description: The list of tags required to enter this room
|
||||
* items:
|
||||
* type: string
|
||||
* example: speaker
|
||||
* textures:
|
||||
* type: array
|
||||
* description: The list of public textures for this map (TODO remove this)
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* todo:
|
||||
* type: string
|
||||
* authenticationMandatory:
|
||||
* type: boolean|null
|
||||
* description: Whether the authentication is mandatory or not for this map.
|
||||
* example: true
|
||||
* roomSlug:
|
||||
* type: string
|
||||
* description: The slug of the room
|
||||
* deprecated: true
|
||||
* example: foo
|
||||
* contactPage:
|
||||
* type: string|null
|
||||
* description: The URL to the contact page
|
||||
* example: https://mycompany.com/contact-us
|
||||
* group:
|
||||
* type: string|null
|
||||
* description: The group this room is part of (maps the notion of "world" in WorkAdventure SAAS)
|
||||
* example: myorg/myworld
|
||||
* iframeAuthentication:
|
||||
* type: string|null
|
||||
* description: The URL of the authentication Iframe
|
||||
* example: https://mycompany.com/authc
|
||||
* expireOn:
|
||||
* type: string|undefined
|
||||
* description: The date (in ISO 8601 format) at which the room will expire
|
||||
* example: 2022-11-05T08:15:30-05:00
|
||||
* canReport:
|
||||
* type: boolean|undefined
|
||||
* description: Whether the "report" feature is enabled or not on this room
|
||||
* example: true
|
||||
*
|
||||
*/
|
||||
this.app.get("/map", (req, res) => {
|
||||
const query = parse(req.path_query);
|
||||
if (typeof query.playUri !== "string") {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import swaggerJsdoc from "swagger-jsdoc";
|
||||
import { BaseHttpController } from "./BaseHttpController";
|
||||
// @ts-ignore
|
||||
import LiveDirectory from "live-directory";
|
||||
import * as fs from "fs";
|
||||
|
||||
export class SwaggerController extends BaseHttpController {
|
||||
routes() {
|
||||
this.app.get("/openapi", (req, res) => {
|
||||
const options = {
|
||||
swaggerDefinition: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "WorkAdventure Pusher",
|
||||
version: "1.0.0",
|
||||
},
|
||||
},
|
||||
apis: ["./src/Controller/*.ts"],
|
||||
};
|
||||
|
||||
res.json(swaggerJsdoc(options));
|
||||
});
|
||||
|
||||
// Create a LiveDirectory instance to virtualize directory with our assets
|
||||
const LiveAssets = new LiveDirectory({
|
||||
path: __dirname + "/../../node_modules/swagger-ui-dist", // We want to provide the system path to the folder. Avoid using relative paths.
|
||||
keep: {
|
||||
extensions: [".css", ".js", ".json", ".png", ".jpg", ".jpeg", ".html"], // We only want to serve files with these extensions
|
||||
},
|
||||
ignore: (path: string) => {
|
||||
return path.startsWith("."); // We want to ignore dotfiles for safety
|
||||
},
|
||||
});
|
||||
|
||||
// Create static serve route to serve index.html
|
||||
this.app.get("/swagger-ui/", (request, response) => {
|
||||
fs.readFile(__dirname + "/../../node_modules/swagger-ui-dist/index.html", "utf8", function (err, data) {
|
||||
if (err) {
|
||||
return response.status(500).send(err.message);
|
||||
}
|
||||
const result = data.replace(/https:\/\/petstore.swagger.io\/v2\/swagger.json/g, "/openapi");
|
||||
|
||||
response.send(result);
|
||||
|
||||
return;
|
||||
});
|
||||
});
|
||||
|
||||
// Create static serve route to serve frontend assets
|
||||
this.app.get("/swagger-ui/*", (request, response) => {
|
||||
// Strip away '/assets' from the request path to get asset relative path
|
||||
// Lookup LiveFile instance from our LiveDirectory instance.
|
||||
const path = request.path.replace("/swagger-ui", "");
|
||||
const file = LiveAssets.get(path);
|
||||
|
||||
// Return a 404 if no asset/file exists on the derived path
|
||||
if (file === undefined) return response.status(404).send("");
|
||||
|
||||
// Set appropriate mime-type and serve file buffer as response body
|
||||
return response.type(file.extension).send(file.buffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ export const OPID_CLIENT_REDIRECT_URL = process.env.OPID_CLIENT_REDIRECT_URL ||
|
||||
export const OPID_PROFILE_SCREEN_PROVIDER = process.env.OPID_PROFILE_SCREEN_PROVIDER || ADMIN_URL + "/profile";
|
||||
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
|
||||
|
||||
// If set to the string "true", the /openapi route will return the OpenAPI definition and the swagger-ui/ route will display the documentation
|
||||
export const ENABLE_OPENAPI_ENDPOINT = process.env.ENABLE_OPENAPI_ENDPOINT === "true";
|
||||
|
||||
export {
|
||||
SECRET_KEY,
|
||||
API_URL,
|
||||
|
||||
Reference in New Issue
Block a user