Skip to content

Commit

Permalink
Adds Callback on SSX Server Middlewares Routes (#105)
Browse files Browse the repository at this point in the history
* Types and methods updated

* Reuse getRoutePath function

* Update import

* Update express middleware example

* Fix HTTP middleware

* Update types and express api example

* Add changelogs

* Add changelogs

---------

Co-authored-by: franklovefrank <[email protected]>
Co-authored-by: Gregory Rocco <[email protected]>
  • Loading branch information
3 people authored Mar 13, 2023
1 parent 57a0c81 commit 94cee9c
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-bikes-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@spruceid/ssx-core': minor
---

This updates the `SSXServerRoutes` type to enable callbacks on ssx server middlewares routes.
20 changes: 20 additions & 0 deletions .changeset/seven-hornets-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@spruceid/ssx-server-middleware': minor
---

This adds callbacks on ssx server middlewares routes. Usage example:

```js
SSXExpressMiddleware( // same to SSXHttpMiddleware
ssx,
{
login: {
path: '/ssx-login',
callback: (req: Request) => {
console.log(`User ${req.body.address} successfully signed in`);
}
},
logout: '/ssx-custom-logout'
}
);
```
10 changes: 9 additions & 1 deletion examples/ssx-test-express-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@ app.use(cors({
origin: true,
}));

app.use(SSXExpressMiddleware(ssx));
app.use(SSXExpressMiddleware(ssx, {
login: {
path: '/ssx-login',
callback: (req: Request) => {
console.log(`User ${req.body.address} successfully signed in`);
}
},
logout: '/ssx-logout'
}));

app.get('/', (req: Request, res: Response) => {
res.send('Express + TypeScript Server');
Expand Down
10 changes: 9 additions & 1 deletion examples/ssx-test-http-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,15 @@ const processRequest = async (req: IncomingMessage, res: ServerResponse) => {
};

// create instance of middleware
const ssxMiddleware = SSXHttpMiddleware(ssx);
const ssxMiddleware = SSXHttpMiddleware(ssx, {
login: {
path: '/ssx-login',
callback: async (req: IncomingMessage, body?: Record<string, any>) => {
console.log(`User ${body?.address} successfully signed in`);
}
},
logout: '/ssx-logout'
});

const requestListener = async (req: IncomingMessage, res: ServerResponse) => {
const optionsOnly = cors(req, res);
Expand Down
25 changes: 20 additions & 5 deletions packages/ssx-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { providers } from 'ethers';
import { ConnectionInfo } from 'ethers/lib/utils';
import { SSXClientSession } from './client';
import type { AxiosRequestConfig } from 'axios';
import type { Request } from 'express';
import type { IncomingMessage } from 'http';

/** SSX Route Configuration
* This configuration is used to override the default endpoint paths.
Expand All @@ -25,17 +27,30 @@ export interface SSXRouteConfig {

/** Type-Guard for SSXRouteConfig. */
export const isSSXRouteConfig = (
config: SSXRouteConfig | AxiosRequestConfig | string
): config is SSXRouteConfig | AxiosRequestConfig => typeof config === 'object';
config: SSXServerRouteEndpointType
): config is SSXRouteConfig | AxiosRequestConfig | SSXServerMiddlewareConfig => typeof config === 'object';


export interface SSXServerMiddlewareConfig {
path: string;
callback?: (req: any, body?: Record<string, any>) => Promise<void> | void;
};

/** Type-Guard for SSXServerMiddlewareConfig. */
export const isSSXServerMiddlewareConfig = (
config: SSXServerRouteEndpointType
): config is SSXServerMiddlewareConfig => (config as SSXServerMiddlewareConfig)?.path !== undefined;

export type SSXServerRouteEndpointType = Partial<SSXRouteConfig> | AxiosRequestConfig | string | SSXServerMiddlewareConfig;

/** Server endpoints configuration. */
export interface SSXServerRoutes {
/** Get nonce endpoint path. /ssx-nonce as default. */
nonce?: Partial<SSXRouteConfig> | AxiosRequestConfig | string;
nonce?: SSXServerRouteEndpointType;
/** Post login endpoint path. /ssx-login as default. */
login?: Partial<SSXRouteConfig> | AxiosRequestConfig | string;
login?: SSXServerRouteEndpointType;
/** Post logout endpoint path. /ssx-logout as default. */
logout?: Partial<SSXRouteConfig> | AxiosRequestConfig | string;
logout?: SSXServerRouteEndpointType;
}

/** Server endpoints name configuration. */
Expand Down
17 changes: 11 additions & 6 deletions packages/ssx-server-middleware/src/express/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import express from 'express';
import { Request, Response } from 'express';
import { SSXServerRouteNames } from '@spruceid/ssx-core';
import { SSXServerRoutes, isSSXServerMiddlewareConfig } from '@spruceid/ssx-core';
import { SSXServerBaseClass } from '@spruceid/ssx-core/server';
import { getRoutePath } from '../utils';

const ssxEndpoints = (
ssx: SSXServerBaseClass,
routes?: SSXServerRouteNames
routes?: SSXServerRoutes
) => {
const router = express.Router();

Expand All @@ -19,11 +20,13 @@ const ssxEndpoints = (
* @param {Response} res
*/
router.get(
routes?.nonce ?? '/ssx-nonce',
getRoutePath(routes?.nonce, '/ssx-nonce'),
function (req: Request, res: Response): void {
req.session.siwe = undefined;
req.session.nonce = ssx.generateNonce();
req.session.save(() => res.status(200).send(req.session.nonce));
isSSXServerMiddlewareConfig(routes?.nonce) ? routes?.nonce?.callback(req) : null;
return;
}
);

Expand All @@ -38,7 +41,7 @@ const ssxEndpoints = (
* @param {Response} res
*/
router.post(
routes?.login ?? '/ssx-login',
getRoutePath(routes?.login, '/ssx-login'),
async function (req: Request, res: Response) {
if (!req.body) {
res.status(422).json({ message: 'Expected body.' });
Expand Down Expand Up @@ -88,6 +91,7 @@ const ssxEndpoints = (
req.session.ens = session.ens;
req.session.lens = session.lens;
req.session.save(() => res.status(200).json({ ...req.session }));
isSSXServerMiddlewareConfig(routes?.login) ? routes?.login?.callback(req) : null;
return;
}
);
Expand All @@ -99,7 +103,7 @@ const ssxEndpoints = (
* @param {Response} res
*/
router.post(
routes?.logout ?? '/ssx-logout',
getRoutePath(routes?.logout, '/ssx-logout'),
async function (req: Request, res: Response) {
try {
req.session.destroy(null);
Expand All @@ -113,9 +117,10 @@ const ssxEndpoints = (
res.status(500).json({ message: error.message });
}
res.status(204).send();
isSSXServerMiddlewareConfig(routes?.logout) ? routes?.logout?.callback(req) : null;
return;
}
);

return router;
};

Expand Down
4 changes: 2 additions & 2 deletions packages/ssx-server-middleware/src/express/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import bodyParser from 'body-parser';
import ssxEndpoints from './endpoints';
import { ssxMiddleware, SSXAuthenticated } from './middleware';
import { SSXServerRouteNames } from '@spruceid/ssx-core';
import { SSXServerRoutes } from '@spruceid/ssx-core';
import { SSXServerBaseClass } from '@spruceid/ssx-core/server';


Expand All @@ -12,7 +12,7 @@ import { SSXServerBaseClass } from '@spruceid/ssx-core/server';
*
* @param ssx - The SSX server instance.
*/
const SSXExpressMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRouteNames) => {
const SSXExpressMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRoutes) => {
return [
ssx.session,
bodyParser.json(),
Expand Down
16 changes: 11 additions & 5 deletions packages/ssx-server-middleware/src/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { SiweGnosisVerify } from '@spruceid/ssx-gnosis-extension';
import { Session, SessionData } from 'express-session';
import { IncomingMessage, ServerResponse } from 'http';
import { SSXRequestObject } from '../express/middleware';
import { SSXServerRoutes } from '@spruceid/ssx-core';
import { isSSXServerMiddlewareConfig, SSXServerRoutes } from '@spruceid/ssx-core';
import { SSXServerBaseClass } from '@spruceid/ssx-core/server';
import { getRoutePath } from '../utils';
import url from 'url';

declare module 'http' {
interface IncomingMessage {
Expand Down Expand Up @@ -81,13 +83,15 @@ export const SSXHttpMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRou
req.session.destroy(() => {});
}
}

const { pathname } = url.parse(req.url)
// ssx endpoints
if (req.url === (routes?.nonce ?? '/ssx-nonce')) {
if (pathname === getRoutePath(routes?.nonce, '/ssx-nonce')) {
req.session.nonce = ssx.generateNonce();
res.statusCode = 200;
res.end(req.session.nonce);
} else if (req.url === (routes?.login ?? '/ssx-login')) {
isSSXServerMiddlewareConfig(routes?.nonce) ? routes?.nonce?.callback(req) : null;
return;
} else if (pathname === getRoutePath(routes?.login, '/ssx-login')) {
// get body data
const body = await getBody(req);
if (!body) {
Expand Down Expand Up @@ -138,13 +142,15 @@ export const SSXHttpMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRou
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ ...req.session }));
} else if (req.url === (routes?.logout ?? '/ssx-logout')) {
isSSXServerMiddlewareConfig(routes?.login) ? routes?.login?.callback(req, body) : null;
} else if (pathname === getRoutePath(routes?.logout, '/ssx-logout')) {
req.session.destroy(null);
req.session = null;
await req.ssx.logout();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ success: true }));
isSSXServerMiddlewareConfig(routes?.logout) ? routes?.logout?.callback(req) : null;
}

// run user defined requestListener
Expand Down
17 changes: 17 additions & 0 deletions packages/ssx-server-middleware/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { isSSXServerMiddlewareConfig, SSXServerRouteEndpointType } from "@spruceid/ssx-core";

/**
* This receives a routeConfig param and returns the path string.
* @param routeConfig - Route config property
* @param defaultPath - Default path string
* @returns a path string
*/
export const getRoutePath = (routeConfig: SSXServerRouteEndpointType, defaultPath: string) => {
if (isSSXServerMiddlewareConfig(routeConfig)) {
return routeConfig.path;
} else if (typeof routeConfig === 'string') {
return routeConfig;
} else {
return defaultPath;
}
};

0 comments on commit 94cee9c

Please sign in to comment.