Releases: boostercloud/booster
Fixed small bugs on read models
What's Changed
- Avoid creating projection if the read model join key is not valid
- Avoid sending an empty object to read the model when a snapshot doesn't exist
Full Changelog: v0.26.7...v0.26.8
Add Event API to Azure Provider
Description
This version add the event API to the Azure Provider
Code and data fields on GraphQL Error messages
Description
This version add code
and data
fields on GraphQL Error messages
Multiple JWT roles
Description
This version improves Booster's Custom Authentication by including the possibility to manage more than one JWT role.
Details
Until now, if we defined the rolesClaim
field as: firebase:groups
this field could only contain a text string. Example:
{
"firebase:groups": "User",
"iss": "https://securetoken.google.com/demoapp",
"aud": "demoapp",
"auth_time": "1604676721",
"user_id": "xJYY5Y6fTbVggNtDjaNh7cNSBd7q1",
"sub": "xJY5Y6fTbVggNtDjaNh7cNSBd7q1",
"iat": 1604676721,
"exp": "1604680321",
"phone_number": "+99999999999",
}
}
It is now possible to also indicate a list of values, such that, if any of the strings match any of the defined roles, the validation will be successful. Example:
{
"firebase:groups": ["Readers", "Writters"],
"iss": "https://securetoken.google.com/demoapp",
"aud": "demoapp",
"auth_time": "1604676721",
"user_id": "xJYY5Y6fTbVggNtDjaNh7cNSBd7q1",
"sub": "xJY5Y6fTbVggNtDjaNh7cNSBd7q1",
"iat": 1604676721,
"exp": "1604680321",
"phone_number": "+99999999999",
}
}
Execution context + JWT token custom validations
This new Booster version contains a context property inside the commands Register
object. In that way, the user could log, intercept or validate at the command side the content of the context object.
@Command({
authorize: 'all',
})
export class CreatePost {
public constructor(
readonly postId: UUID,
readonly title: string,
readonly content: string,
readonly author: string
) {}
public static async handle(command: CreatePost, register: Register): Promise<void> {
console.log('Our awesome context', register.context)
register.events(new PostCreated(command.postId, command.title, command.content, command.author))
}
}
Also, we support a new extraValidation
function inside the TokenVerifierConfig
to perform custom JWT token validations which will be executed always after the JWT standard validations, expiration, issuers checks, and so on.
This is the new signature for TokenVerifierConfig:
export type TokenVerifierConfig = {
issuer: string
jwksUri?: string
publicKey?: string
rolesClaim?: string
extraValidation?: (jwtToken: unknown, rawToken: string) => void
}
The extraValidation
function will receive the decoded token jwtToken
which includes the header, payload, and signature. Also, the raw token is provided for additional checks. This extraValidation
function must throw an exception if any custom validation doesn't match.
Example config:
const configWithExtraValidation = new BoosterConfig('test with extra validation')
configWithExtraValidation.tokenVerifiers = [
{
issuer: 'auth0',
jwksUri: 'https://myauth0app.auth0.com/.well-known/jwks.json',
extraValidation: (jwtToken, _rawToken) => {
if ((jwtToken.headers as any)?.alg !== 'RS512') {
throw 'Invalid token encoding'
}
if ((jwtToken.payload as any)?.['custom:role'] !== 'Admin') {
throw 'Unauthorized'
}
},
},
]
Rockets 2.0 - Multi-provider Rockets
Rockets 2.0
Rockets 2.0 is here!
This new Booster version includes the new Rockets 2.0 functionality that allows you to create multi-providers Rockets.
A Multi-providers Rocket
is a Rocket that includes an implementation for different vendors in the same Rocket. For example, you can create a Rocket to handle webhooks that works for Azure or Local Provider in the same Rocket.
This functionality supports the following providers:
- Azure Provider
- Local Provider
New improvement with a new Terraform provider
What's Changed
This release introduces a new Azure provider for Booster, which makes deploying your applications more reliable by using Terraform.
Thanks to this new provider, Azure supports will come close to the AWS provider sooner than expected π πͺ .
And, you can now create Terraform templates with a new Booster synth
command.
Full Changelog: v0.21.7...v0.22.0
Sequenced Read Models and improved Read Model Before Hooks.
This release introduces two features:
Sequenced Read Models
This release introduces the ability to model read models as time sequenced (Or time series). This feature is useful to model a series of data objects that are indexed by the time they were logged. This might sound similar to events, but sequenced read models are also mutable, so they can change over time without losing their place in the sequence. An example use case could be a chat application, where all messages are identified by a specific channel ID and a timestamp of when they were sent, but individual messages can be edited or deleted.
Booster provides a special decorator to tag a specific property as a sequence key for a read model:
export class MessageReadModel {
public constructor(
readonly id: UUID, // A channel ID
@sequencedBy readonly timestamp: string,
readonly contents: string
)
@Projects(Message, 'id')
public static projectMessage(entity: Message, currentReadModel: MessageReadModel): ProjectionResult<MessageReadModel> {
return new MessageReadModel(entity.id, entity.timestamp, entity.contents)
}
}
Adding a sequence key to a read model changes the behavior of its query, which now accepts the sequence key as an optional parameter:
query MessageReadModel(id: ID!, timestamp: string): [MessageReadModel]
In this query, when only the id
is provided, you get an array of all the messages in the channel ordered by timestamp in ascending order (from older to newer). When you also provide a specific timestam
, you still get an array, but it will only contain the specific message sent at that time.
You can read more about sequenced read models in the documentation.
Improved Read Model Before Hooks
Read Model Before Hooks allow you to set up one or more functions that are called before a read model request is processed, having the opportunity of altering or canceling the request. A common use case for read model before hooks is managing complex access restrictions policies that depend on the business logic, like allowing a user to access objects they own but not objects they should not have access to.
Starting from this release, before hooks will receive the entire request object received from GraphQL, and not only the filters, opening the door to more complex rewrites of specific queries. Before hook functions will receive and return ReadModelRequestEnvelope
objects which have the following signature:
interface ReadModelRequestEnvelope<TReadModel> {
currentUser?: UserEnvelope // The current authenticated user
requestID: UUID // An ID assigned to this request
key?: { // If present, contains the id and sequenceKey that identify a specific read model
id: UUID
sequenceKey?: SequenceKey
}
className: string // The read model class name
filters: ReadModelRequestProperties<TReadModel> // Filters set in the GraphQL query
limit?: number // Query limit if set
afterCursor?: unknown // For paginated requests, id to start reading from
}
Fixes in the GraphQL input types
In the latest releases, the GraphQL schema generation was improved by adding GraphQLNonNull
to elements of arrays.
However, this caused arrays in input types to result in JSONObject
instead of the specific type. (e9906b4)
What happened in the last release
Here's what happened
It also broke PR #890 which also fixes some JSONObject
types, returning specific types instead.
[Booster] Error: The type of UpsertDossierInput.parties must be Input Type but got: [DossierParty!].
With this release the input types are improved again, so they behave as expected:
Command handlers that return values
In this release we introduce the @Returns
decorator that allows you to define the return type of a command handler.
This value will be returned to the client through GraphQL, instead of just returning true
(that's left for the case of void
).
As an example:
@Command({
authorize: 'all',
})
export class CreateProduct {
public constructor(readonly sku: string, readonly price: number) {}
@Returns(String)
public static async handle(command: CreateProduct, register: Register): Promise<string> {
return "Product created!"
}
}
Learn more in the Booster documentation