Security News
Supply Chain Attack Detected in Solana's web3.js Library
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
advanced-controllers
Advanced tools
Features:
@Decorators
async
functions and PromisesSee the tests for examples. See the Change log for breaking changes.
Inspired on ASP.NET MVC it is possible to create Express based controllers and actions.
import * as web from 'advanced-controllers';
@web.Controller('/kitten')
class KittenController extends AdvancedController {
// GET /kitten/all
@web.Get('/all')
getAll() { }
// GET /kitten/details
@web.Get()
details() {}
// POST /kitten/create
@web.Post()
create() {}
// DELETE /kitten/delete
@web.Del('delete')
deleteKitten() {}
}
let kittenCtrl = new KittenController();
kittenCtrl.register(expressApp);
Features:
@Controller('name')
decorator on the controller classGet
, Post
, Put
, Head
, Options
, Del
/
character in the names is optionalCaveats:
@Get()
, bad: @Get
@Del
instead of @Delete
AdvancedController
and call the register()
functionbody-parser
for your Express app (so body won't be parsed)You can register a single controller or all AdvancedController instances created so far.
kittenCtrl.register(
expressApp, // Express app
{}, // Optional settings
);
// OR
AdvancedController.registerAll(expressApp, {});
Optional settings
can have these props:
namespace
: Prefix for the routes (E.g. namespace1
--> /namespace1/ctrl/action
)debugLogger
: Debug logger for registrationerrorLogger
: Any runtime errors (E.g. Errors / promise rejects in actions -- except WebError
instances, see below at the WebError
section)implicitPublic
: See below at PermissionsCaveats:
AdvancedController.register
, use it once and do not use the instances' register
methods.AdvancedController.register
works on instantiated controllers.Tired of calling and validating let myStuff = req.body.someVariable
in every function? Well, we try to make it a bit more comfortable. We have some limitations though but here's what we've got:
import * as web from 'advanced-controllers';
@web.Controller('/kitten')
class KittenController extends web.AdvancedController {
// GET /kittens/all?from=0[&cnt=25]
@web.Get('/all')
getKittens(
@web.Query('from', Number) from: number,
@web.Query('cnt', Number, true) cnt: number
) { }
// POST /kittens/create, body: { kitten: {} }
@web.Post()
create(
@web.Body('kitten', Object) kitten: Kitten
) {}
// POST /kittens/create2, body: {}
@web.Post()
create2(@web.Body() kitten: Kitten)
// DELETE /kittens/delete/<id>
@web.Del('delete/:id')
deleteKitten(@web.Param('id') id: string) {}
}
Interface
// Whole body
export function Body(type?: any): ActionDecorator;
// Member of body object
export function Body(name: string, type?: any, optional?: boolean): ActionDecorator;
// Query winthout type
export function Query(name: string, optional?: boolean): ActionDecorator;
// Query with type
export function Query(name: string, type: any, optional?: boolean): ActionDecorator;
// Param with or without type
export function Param(name: string, type?: any): ActionDecorator;
Features:
Body
: only validationQuery
and Param
: parse + validationString
, Number
, Object
, Array
Query
and Param
then the parameter value will be a string
Query
and Param
: Objects and arrays in query MUST be JSON-serialized. But seriously... arrays and objects in query?optional
parameter to true
Caveats:
@Body()
, bad: @body
You can access the original req
or res
objects with similar syntax. Beware: if you ask the res
object then we won't handle the return values for you. (Return values are discussed soon.)
@web.Controller('casual')
class CasualController extends web.AdvancedController {
@web.Get('fancy-function')
fancyFunction(
@web.Req() req: web.Request,
@web.Res() res: web.Response
) {}
}
Authenticated Users
There's a @Auth
shorthand which returns the request.auth
object (or undefined
). The value of the user is usually set in an express middleware such as express-jwt
.
Note: express-jwt
versions below 7 used request.user
, but from 7+ they use request.auth
. The @User
shorthand (deprecated) returns whichever it finds, the @Auth
works with versions 7+.
@web.Controller('casual2')
class CasualController extends web.AdvancedController {
@web.Get('latest-fancy-function')
latestFancyFunction(@web.Auth() auth?: { id: string }) {
console.log(`Gotcha: ${user ? user.id : 'nevermind'}`);
}
// Deprecated
@web.Get('another-fancy-function')
anotherFancyFunction(@web.User() user?: { id: string }) {
console.log(`Gotcha: ${user ? user.id : 'nevermind'}`);
}
}
Caveats:
res
then you have to manually end the request, e.g. res.send('')
(see next section)@Req()
, bad: @Req
@Auth
and the @User()
decorators return undefined
by default. You'll need an express-jwt
to have anything there.By default the response is closed automatically with a status code and sometimes with data
undefined
: 200string
types: 200 + result as body (raw string)JSON.strigify
-edstatusCode
property ending with statusCode
WebError
class for thisjson
field then it will be sent as JSONtext
field then it will be sent as plain text{ "errors": [ { "message": "some-stuff" }]}
Caveats:
res
then there is no auto-close. In this case we don't know whether the response is closed -- or will be closed -- in the action. The only exception is if the action throws an error: in that case we apply the regular error handler logic.WebError
objectThis object extends Error
and can be used to conveniently send back custom HTTP codes, error messages and codes. Feel free to throw it in actions, the framework will handle it.
new WebError(message: string)
, sending HTTP status code 500 by defaultnew WebError(message: string, statusCode: number)
new WebError(message: string, settings: { statusCode?: number, errorCode?: number | string})
, the errorCode
will be in the result JSON as errors[0].code
The result will be something like this: { "errors": [ { "message": "some-stuff", "code": 1 }]}
+ HTTP 400 header
Customization by overwriting WebError.requestErrorTransformer
.
One extra functionality is the utilization of express.use()
. You can specify middleware calls over the actions.
@web.Controller('middleware')
class MiddlewareTestController extends web.AdvancedController {
middleware(req: web.Request, res: web.Response, next: Function) {
console.log('Middleware called');
next();
}
@web.Get('do-stuff')
@web.Middleware('middleware')
doStuff() {}
@web.Get('do-more-stuff')
@web.Middleware(otherMiddlewareFunction)
doMoreStuff() {}
}
Functions:
this
reference will be the controller instanceLimitations:
Caveats:
() => {}
) then the this
reference won't refer to the controller instance when the middleware is called. The reason is TS/ES6 to ES5 transpilation: the this
reference changes in the process and I could not bind it.Action authorization can be controlled by the following decorators. Controller classes and action functions can be decorated (the latter is stronger). An action will have a single permission. (You shouldn't use multiple decorators on the same class or action function.)
@Permission(name?: string)
: The action requires the name
permission (default value: ctrl.action
)@Authorize()
: The action requires an authenticated user, i.e. req.user
object must not be undefined
@Public()
: The action does not requireThe permission check can be managed 2 ways.
req.user.hasPermission(permission: string): boolean | Promise<booleam>
. You can implement it however you'd like toAdvancedController.setRoles(roles: { name: string, permissions: string[] }[])
function to set the roles and their permissions. The following array should exists: req.user.roles: string[]
export interface RequestWithUser extends Request {
user: {
// Default: custom authorization
hasPermission?(permission: string): boolean | Promise<boolean>;
// Role-based authorization
roles?: string[]
};
}
You should create the req.user.hasPermission
function OR the req.user.roles
array in your custom authorization middleware.
If you don't use permissions (@Permission
or @Authorize
or @Public
) then you can ignore this subsection.
If there are permission-related decorators in your app then you shall do at least one of the following to avoid auto-authentication:
@Public
decoratorimplicitPublic
, e.g.: AdvancedController.regiseterAll(app, { implicitPublic: true })
This is to prevent unintentional publication of some of your actions by forgetting the @Permission
decorator.
You can use permissions like this:
// You can annotate this
@web.Controller('perm')
class PermissionController extends web.AdvancedController {
// GET /perm
// Needs permission: 'perm.testEmpty'
@web.Permission()
@web.Get('')
testEmpty { return { done: true }; }
// GET /perm/test1-a
// Needs permission: 'perm.test1-a'
@web.Permission()
@web.Get('test1-a')
testOneA() { return { done: true }; }
// GET /perm/testOneB
// Needs permission: 'perm.TestOneB'
@web.Permission()
@web.Get()
testOneB() { return { done: true }; }
// POST /perm/test2
// Needs permission: 'perm.test-two'
@web.Permission('perm.test-two')
@web.Post('test2')
testTwo() { return { done: true }; }
// GET /perm/noPerm
// NO permission required
@web.Get('noperm')
@web.Public()
noPerm() { return { done: true }; }
// GET /perm/authorized
// Must be authenticated, but no explicit permission required
@web.Get('authorized')
@web.Authorize()
authorized() { return { done: true}; }
}
The following static functions help the management of authorization:
AdvancedController.getAllPermissions(): string[]
: Aggregates the getPermission()
results for all AdvancedController
instances.AdvancedController.getAllPublicRoutes(): string[]
: Aggregates the getPublicRoutes()
results for all AdvancedController
instances. This can be used to create a whitelist in he authentication middleware (e.g. skip JWT checks on these URLs). Note that the results contain /ctrl/action
style URLs but they do NOT contain the namespace
if there is any (i.e. NOT /namespace/ctrl/action
).Note that the static functions work on instantiated controllers only.
Notes:
hasPermission
. After AdvancedController.setRoles
is called: role-based.req.user
does not exists) the response is: 401 { errors: [{ message: "Unauthenticated" }]}
.{ errors: [{ message: "Unauthorized" }]}
.See [HTTP Status Codes Wiki][https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error].
Caveats:
req.user.hasPermission
or req.user.roles
before registering the controller.''
)FAQs
Features:
We found that advanced-controllers demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.