Skip to content

Commit

Permalink
refactoring (#9)
Browse files Browse the repository at this point in the history
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
  • Loading branch information
mmacxm committed Aug 26, 2022
1 parent c621fec commit f9e7e48
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 259 deletions.
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@ or in your package.json:
// Options are pulled from the environment
import StyraRun from "styra-run"

const options = {
https: process.env.RUN_URL
token: process.env.RUN_TOKEN
}
const client = StyraRun.New(options)
const client = StyraRun(process.env.RUN_URL, process.env.RUN_TOKEN)
```

### Query

Makes a policy rule query, returning the result dictionary: `{"result": any}`
Makes a policy rule query, returning the result object: `{"result": any}`

```javascript
const input = {...}
Expand All @@ -56,7 +52,7 @@ client.query('foo/bar/allowed', input)

### Check

Makes a policy rule query, returning `true` if the result dictionary equals `{"result": true}`, `false` otherwise.
Makes a policy rule query, returning `true` if the result object equals `{"result": true}`, `false` otherwise.

```javascript
const input = {...}
Expand Down Expand Up @@ -98,7 +94,7 @@ However, the default predicate can be overridden:

```javascript
const input = {...}
// Predicate that requires the policy rule to return a dictionary containing a `{"role": "admin"}` entry.
// Predicate that requires the policy rule to return a object containing a `{"role": "admin"}` entry.
const myPredicate = (response) => {
return response?.result?.role === 'admin'
}
Expand Down Expand Up @@ -203,7 +199,7 @@ import {Router} from 'express'

const router = Router()

router.post('/authz', client.proxy(async (req, res, path, input) => {
router.post('/authz', client.proxy(onProxy: async (req, res, path, input) => {
return {
...input,
subject: req.subject, // Add subject from session
Expand All @@ -215,7 +211,7 @@ export default {
}
```
The `proxy(onProxy)` function takes a callback function as argument, and returns a request handling function. The provided callback function takes as arguments the incoming HTTP `Request`, the outgoing HTTP `Response`, the `path` of the queried policy, and the, possibly incomplete, `input` document for the query. The callback must return an updated version of the provided `input` document.
The `proxy()` function takes a callback function as argument, and returns a request handling function. The provided callback function takes as arguments the incoming HTTP `Request`, the outgoing HTTP `Response`, the `path` of the queried policy, and the, possibly incomplete, `input` document for the query. The callback must return an updated version of the provided `input` document.
### RBAC Management API
Expand Down Expand Up @@ -245,3 +241,23 @@ The RBAC API exposes the following endpoints:
| `<API route>/roles` | `GET` | Get a list of available roles. Returns a json list of strings; e.g. `["ADMIN","VIEWER"]`. |
| `<API route>/user_bindings` | `GET` | Get user to role bindings. Returns a list of dictionaries, where each entry has two attributes: the `id` of the user; and their `roles`, as a list of role string identifiers; e.g. `[{"id": "alice", "roles": ["ADMIN"]}, {"id": "bob", "roles": ["VIEWER"]}]`. `GET` requests to this endpoint can include the `page` query attribute; an `integer` indicating what page of bindings to enumerate. The page size is defined when creating the API request handler on the server by calling `manageRbac`. |
| `<API route>/user_bindings/<id>` | `PUT` | Sets the role bindings of a user, where the `<id>` path component is the ID of the user. The request body must be a json list string role identifiers; e.g. `['ADMIN', 'VIEWER']`.
/*
it would be nice to provide an example of a middleware to authorize protected endpoints
middlewares are Express specific I believe
example:
router.use(async function hasManagePermissions (req, res, next) {
const isAllowed = await client.check(...);
if (isAllowed) {
next()
} else {
res.sendStatus(401)
}
});
should also provide the min version of Node.js this SDK supports?
Node 18 is current and will be active LTS https://nodejs.org/en/about/releases/ when we probably release this SDK
*/
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "styra-run-sdk-node",
"version": "0.0.2",
"description": "The Styra Run SDK for node.js",
"description": "Styra Run Node.js SDK",
"author": "Styra Inc.",
"license": "Apache-2.0",
"type": "module",
"main": "./src/run-sdk.js",
"scripts": {
"test": "jasmine"
"test": "jasmine --config=tests/support/jasmine.json"
},
"homepage": "https://github.com/StyraInc/styra-run-sdk-node#readme",
"repository": {
Expand Down
23 changes: 12 additions & 11 deletions src/api-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import Url from "url"
import { AwsClient } from "./aws.js"
import { StyraRunError } from "./errors.js"
import { httpRequest, urlToRequestOptions } from "./helpers.js"
import { API_CLIENT_MAX_RETRIES, AWS_IMDSV2_URL } from "./constants.js"

// TODO: Re-fetch gateway list after some time (?)
// TODO: Make it configurable to cap retry limit at gateway list size (?)

export class ApiClient {
constructor(url, token, {
organizeGateways = makeOrganizeGatewaysCallback(),
maxRetries = 3
maxRetries = API_CLIENT_MAX_RETRIES
} = {}) {
this.url = Url.parse(url)
this.token = token
Expand All @@ -19,7 +20,7 @@ export class ApiClient {

async get(path) {
return await this.requestWithRetry({
path: path,
path,
method: 'GET',
headers: {
'authorization': `bearer ${this.token}`
Expand All @@ -29,7 +30,7 @@ export class ApiClient {

async put(path, data) {
return await this.requestWithRetry({
path: path,
path,
method: 'PUT',
headers: {
'content-type': 'application/json',
Expand All @@ -40,7 +41,7 @@ export class ApiClient {

async post(path, data) {
return await this.requestWithRetry({
path: path,
path,
method: 'POST',
headers: {
'content-type': 'application/json',
Expand All @@ -51,7 +52,7 @@ export class ApiClient {

async delete(path) {
return await this.requestWithRetry({
path: path,
path,
method: 'DELETE',
headers: {
'authorization': `bearer ${this.token}`
Expand Down Expand Up @@ -120,7 +121,7 @@ export class ApiClient {
return undefined
}
})
.filter((entry) => entry !== undefined)
.filter((entry) => !!entry)



Expand All @@ -133,24 +134,24 @@ export class ApiClient {
}
}

export function makeOrganizeGatewaysCallback(metadataServiceUrl = 'http://169.254.169.254:80') {
export function makeOrganizeGatewaysCallback(metadataServiceUrl = AWS_IMDSV2_URL) {
const awsClient = new AwsClient(metadataServiceUrl)
return async (gateways) => {
// NOTE: We assume zone-id:s are unique across regions
const {region, zoneId} = await awsClient.getMetadata()
if (region === undefined && zoneId === undefined) {
if (!region && !zoneId) {
return gateways
}

const copy = [...gateways]
return copy.sort((a, b) => {
if (zoneId !== undefined && a.aws?.zone_id === zoneId) {
if (zoneId && a.aws?.zone_id === zoneId) {
// always sort matching zone-id higher
return -1
}
if (region !== undefined && a.aws?.region === region) {
if (region && a.aws?.region === region) {
// only sort a higher if b doesn't have a matching zone-id
return (zoneId !== undefined && b.aws?.zone_id === zoneId) ? 1 : -1
return (zoneId && b.aws?.zone_id === zoneId) ? 1 : -1
}
return 0
})
Expand Down
7 changes: 4 additions & 3 deletions src/aws.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Url from "url"
import { StyraRunHttpError } from "./errors.js"
import { httpRequest, joinPath, urlToRequestOptions } from "./helpers.js"
import { AWS_IMDSV2_URL, AWS_IMDSV2_TOKEN_TTL } from "./constants.js"

const TOKEN_PATH = '/latest/api/token'
const METADATA_PATH = '/latest/meta-data'
Expand All @@ -10,7 +11,8 @@ function is401Error(err) {
}

export class AwsClient {
constructor(url = 'http://169.254.169.254:80', tokenTtl = 21600) {
// separate file for all constant configs?
constructor(url = AWS_IMDSV2_URL, tokenTtl = AWS_IMDSV2_TOKEN_TTL) {
this.reqOpts = urlToRequestOptions(Url.parse(url))
this.tokenTtl = tokenTtl
}
Expand Down Expand Up @@ -39,7 +41,7 @@ export class AwsClient {

async getToken() {
if (this.tokenIsUnsupported) {
return undefined
return
}

if (this.token) {
Expand All @@ -60,7 +62,6 @@ export class AwsClient {
return this.token
} catch (err) {
this.tokenIsUnsupported = true
return undefined
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// TODO: Organize into enums?
export const BATCH_MAX_ITEMS = 20
export const AWS_IMDSV2_URL = 'http://169.254.169.254:80'
export const AWS_IMDSV2_TOKEN_TTL = 21600
export const API_CLIENT_MAX_RETRIES = 3

12 changes: 0 additions & 12 deletions src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ export class StyraRunError extends Error {
this.name = "StyraRunError"
this.cause = cause
}

isStyraRunError() {
return true
}
}

/**
Expand All @@ -23,10 +19,6 @@ export class StyraRunAssertionError extends StyraRunError {
super(NOT_ALLOWED)
this.name = "StyraRunAssertionError"
}

isStyraRunAssertionError() {
return true
}
}

/**
Expand All @@ -40,10 +32,6 @@ export class StyraRunHttpError extends StyraRunError {
this.body = body
}

isStyraRunHttpError() {
return true
}

isNotFoundStatus() {
return this.statusCode === 404
}
Expand Down
Loading

0 comments on commit f9e7e48

Please sign in to comment.