Skip to content

SciSpike/securable-trait

Repository files navigation

securable-trait

This trait allows an object to secure itself, so that you can ask questions like "Can Sally read Bob's account balance?" or "Can bank teller John close account #123?"

The primary export of this module is a trait called Securable, which you can have your class(es) express. See our @ballistagroup/mutrait package for more information, including full trait support in JavaScript.

TL;DR

File Account.js:

const { traits } = require('@ballistagroup/mutrait')
const { Securable } = require('@ballistagroup/securable-trait')

class Account extends traits(Securable) {
  constructor (balance = 0, openedAt = new Date()) {
    super(balance, openedAt)
    this._balance = balance
    this.openedAt = openedAt
    this.closedAt = null
  }

  get balance () { return this._balance }
  deposit (amount) { this._balance += amount } 
  withdraw (amount) { this._balance -= amount }
  close (at = new Date()) { this.closedAt = at }
  get open () { return !this.closed }
  get closed () { return !!this.closedAt }
}

module.exports = Account

File index.js:

const Account = require('./Account')

const sally = 'sally'
const keith = 'keith'
const acct = new Account()

console.log(acct.secured) // false
console.log(acct.grants({ principals: sally, actions: 'get balance' })) // true
console.log(acct.grants({ principals: sally, actions: 'close' }))       // true
console.log(acct.grants({ principals: keith, actions: 'get balance' })) // true
console.log(acct.grants({ principals: keith, actions: 'close' }))       // true

acct.grant({ principal: sally, action: 'get balance' })
acct.grant({ principal: sally, action: 'close' })

console.log(acct.secured) // true

console.log(acct.grants({ principals: sally, actions: 'get balance' })) // true
console.log(acct.grants({ principals: keith, actions: 'close' }))       // false
console.log(acct.grants({ principals: sally, actions: 'get balance' })) // true
console.log(acct.grants({ principals: keith, actions: 'close' }))       // false

Security via aspect-oriented programming (AOP)

When you combine this library with method interception via @ballistagroup/aspectify and with ClsHookedContext or ZoneJsContext from @ballistagroup/continuation-local-storage, you can completely isolate the crosscutting concern of security.

File Secured.js:

const { Before } = require('@ballistagroup/aspectify')
const Context = require('@ballistagroup/continuation-local-storage/context/ClsHookedContext')

const secured = Before(({ thisJoinPoint }) => {
  if (!thisJoinPoint.thiz || thisJoinPoint?.thiz === thisJoinPoint?.clazz) {
    return // because we're in a static context or there's no securable to call securable.grants on
  }
    
  const token = Context().get('token')

  if (!thisJoinPoint.thiz.grants({
    principals: token.principal,
    actions: thisJoinPoint.fullName
  })) {
    const e = new Error(`E_UNAUTHORIZED`)
    e.principal = token.principal
    e.clazz = thisJoinPoint.clazz.constructor.name
    e.method = thisJoinPoint.fullName
    
    throw e
  }
})

module.exports = secured

File Account.js:

const { traits } = require('@ballistagroup/mutrait')
const { Securable } = require('@ballistagroup/securable-trait')
const secured = require('./secured')

class Account extends traits(Securable) {
  constructor (balance = 0, openedAt = new Date()) {
    super(balance, openedAt)
    this._balance = balance
    this.openedAt = openedAt
    this.closedAt = null
  }

  @secured
  get balance () { return this._balance }

  @secured
  deposit (amount) { this._balance += amount } 

  @secured
  withdraw (amount) { this._balance -= amount }

  @secured
  close (at = new Date()) { this.closedAt = at }

  @secured
  get open () { return !this.closed }

  @secured
  get closed () { return !!this.closedAt }
}

module.exports = Account

TODO

There's more to write here, but for now, see the tests for usage.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published