Skip to content

Commit

Permalink
VEGA-2260 Record updates in changes table #minor (#98)
Browse files Browse the repository at this point in the history
* Update example to latest LPA spec

* Fix broken API functional test

* Make local dynamodb visible to NoSQL workbench

See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.UsageNotes.html

* Modify PUT to use a transaction

* Add changes table and wiring into client

* Create changes table locally and align terraform to it

* Separate method for put with logging

* Reinstate original put method

* Return claims from JWT verifier

This is so that we can log the subject recovered from the JWT.

* Rename field to applied

This records the datetime when the update was applied.

* Store extra request data in changes log

* Fix tests for new JWT verifier API

* Terraform fix

* Another terraform fix

* Create errors in a consistent way

* Remove spurious logging

* Fix typo in JSON serialisation

* Add identifier to each update

* Check update object is properly constructed

* Fix lint error

* Prevent 'struct literal uses unkeyed fields' static analysis error

---------

Co-authored-by: Elliot Smith <[email protected]>
  • Loading branch information
townxelliot and Elliot Smith authored Feb 5, 2024
1 parent eda6da1 commit 1517085
Show file tree
Hide file tree
Showing 18 changed files with 319 additions and 126 deletions.
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test-api:
./api-test/tester -expectedStatus=401 REQUEST GET $(URL)/lpas/$(LPA_UID) '' && \
cat ./docs/example-lpa.json | ./api-test/tester -jwtSecret=$(JWT_SECRET_KEY) -expectedStatus=201 REQUEST PUT $(URL)/lpas/$(LPA_UID) "`xargs -0`" && \
./api-test/tester -jwtSecret=$(JWT_SECRET_KEY) -expectedStatus=400 REQUEST PUT $(URL)/lpas/$(LPA_UID) '{"version":"2"}' && \
./api-test/tester -jwtSecret=$(JWT_SECRET_KEY) -expectedStatus=201 REQUEST POST $(URL)/lpas/$(LPA_UID)/updates '{"type":"CHANGE_NAME","changes":[{"key":"/donor/surname","old":"Zoller","new":"Kjar"}]}' && \
cat ./docs/certificate-provider-change.json | ./api-test/tester -jwtSecret=$(JWT_SECRET_KEY) -expectedStatus=201 REQUEST POST $(URL)/lpas/$(LPA_UID)/updates "`xargs -0`" && \
./api-test/tester -jwtSecret=$(JWT_SECRET_KEY) -expectedStatus=200 REQUEST GET $(URL)/lpas/$(LPA_UID) ''
.PHONY: test-api

Expand All @@ -41,11 +41,11 @@ create-tables:
--key-schema AttributeName=uid,KeyType=HASH \
--billing-mode PAY_PER_REQUEST

docker compose run --rm aws dynamodb describe-table --table-name events || \
docker compose run --rm aws dynamodb describe-table --table-name changes || \
docker compose run --rm aws dynamodb create-table \
--table-name events \
--attribute-definitions AttributeName=uid,AttributeType=S AttributeName=created,AttributeType=S \
--key-schema AttributeName=uid,KeyType=HASH AttributeName=created,KeyType=RANGE \
--table-name changes \
--attribute-definitions AttributeName=uid,AttributeType=S AttributeName=applied,AttributeType=S \
--key-schema AttributeName=uid,KeyType=HASH AttributeName=applied,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST

run-structurizr:
Expand Down
6 changes: 6 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ version: "3.6"
services:
ddb:
image: amazon/dynamodb-local:latest
command: -jar DynamoDBLocal.jar -sharedDb -dbPath .
ports:
- "8000:8000"

lambda-create:
depends_on: [ddb]
Expand All @@ -17,6 +20,7 @@ services:
AWS_ACCESS_KEY_ID: X
AWS_SECRET_ACCESS_KEY: X
DDB_TABLE_NAME_DEEDS: deeds
DDB_TABLE_NAME_CHANGES: changes
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret}
volumes:
- "./lambda/.aws-lambda-rie:/aws-lambda"
Expand All @@ -35,6 +39,7 @@ services:
AWS_ACCESS_KEY_ID: X
AWS_SECRET_ACCESS_KEY: X
DDB_TABLE_NAME_DEEDS: deeds
DDB_TABLE_NAME_CHANGES: changes
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret}
volumes:
- "./lambda/.aws-lambda-rie:/aws-lambda"
Expand All @@ -53,6 +58,7 @@ services:
AWS_ACCESS_KEY_ID: X
AWS_SECRET_ACCESS_KEY: X
DDB_TABLE_NAME_DEEDS: deeds
DDB_TABLE_NAME_CHANGES: changes
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret}
volumes:
- "./lambda/.aws-lambda-rie:/aws-lambda"
Expand Down
30 changes: 30 additions & 0 deletions docs/certificate-provider-change.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"type": "CERTIFICATE_PROVIDER_SIGN",
"changes": [
{
"key": "/certificateProvider/address/line1",
"new": "98 CVVVV",
"old": null
},
{
"key": "/certificateProvider/address/town",
"new": "Murkkkk Town",
"old": null
},
{
"key": "/certificateProvider/address/country",
"new": "GB",
"old": null
},
{
"key": "/certificateProvider/signedAt",
"new": "2024-01-13T22:00:00Z",
"old": null
},
{
"key": "/certificateProvider/contactLanguagePreference",
"new": "en",
"old": null
}
]
}
65 changes: 33 additions & 32 deletions docs/example-lpa.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
{
"lpaType": "personal-welfare",
"donor": {
"firstNames": "Homer",
"surname": "Zoller",
"dateOfBirth": "1960-04-06",
"email": "[email protected]",
"firstNames": "Feeg",
"lastName": "Bundlaaaa",
"address": {
"line1": "79 Bury Rd",
"town": "Hampton Lovett",
"postcode": "WR9 2PF",
"line1": "74 Cloob Close",
"town": "Mahhhhhhhhhh",
"country": "GB"
}
},
"dateOfBirth": "1970-01-24",
"email": "[email protected]"
},
"attorneys": [
{
"firstNames": "Jake",
"surname": "Vallar",
"dateOfBirth": "2001-01-17",
"email": "[email protected]",
"status": "active",
"address": {
"line1": "71 South Western Terrace",
"town": "Milton",
"postcode": "6306",
"country": "AU"
}
},
{
"firstNames": "Tory",
"surname": "Lapolla",
"dateOfBirth": "1989-12-29",
"status": "replacement",
"firstNames": "Herman",
"lastName": "Seakrest",
"address": {
"line1": "184 Bayside Apartments",
"line2": "East Street",
"line3": "Saratosa",
"town": "Polebrook",
"country": "US"
}
"line1": "81 NighOnTimeWeBuiltIt Street",
"town": "Mahhhhhhhhhh",
"country": "GB"
},
"dateOfBirth": "1982-07-24",
"email": "[email protected]",
"status": "active"
}
]
],
"certificateProvider": {
"firstNames": "Vone",
"lastName": "Spust",
"address": {
"line1": "122111 Zonnington Way",
"town": "Mahhhhhhhhhh",
"country": "GB"
},
"channel": "online",
"email": "[email protected]"
},
"lifeSustainingTreatmentOption": "option-a",
"signedAt": "2024-01-10T23:00:00Z",
"certificateProviderNotRelatedConfirmedAt": "2024-01-11T22:00:00Z"
}

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.27.0
github.com/aws/aws-xray-sdk-go v1.8.3
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.6.0
github.com/ministryofjustice/opg-go-common v0.0.0-20231128145056-24628fba649c
github.com/stretchr/testify v1.8.4
Expand Down
26 changes: 0 additions & 26 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,14 @@ github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1M
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/aws/aws-lambda-go v1.43.0 h1:Tdu7SnMB5bD+CbdnSq1Dg4sM68vEuGIDcQFZ+IjUfx0=
github.com/aws/aws-lambda-go v1.43.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
github.com/aws/aws-lambda-go v1.44.0 h1:Xp9PANXKsSJ23IhE4ths592uWTCEewswPhSH9qpAuQQ=
github.com/aws/aws-lambda-go v1.44.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
github.com/aws/aws-lambda-go v1.45.0 h1:3xS35Dlc8ffmcwfcKTyqJGiMuL0UDvkQaVUrI5yHycI=
github.com/aws/aws-lambda-go v1.45.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
github.com/aws/aws-lambda-go v1.46.0 h1:UWVnvh2h2gecOlFhHQfIPQcD8pL/f7pVCutmFl+oXU8=
github.com/aws/aws-lambda-go v1.46.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
github.com/aws/aws-sdk-go v1.49.13 h1:f4mGztsgnx2dR9r8FQYa9YW/RsKb+N7bgef4UGrOW1Y=
github.com/aws/aws-sdk-go v1.49.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.15 h1:aH9bSV4kL4ziH0AMtuYbukGIVebXddXBL0cKZ1zj15k=
github.com/aws/aws-sdk-go v1.49.15/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.16 h1:KAQwhLg296hfffRdh+itA9p7Nx/3cXS/qOa3uF9ssig=
github.com/aws/aws-sdk-go v1.49.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.17 h1:Cc+7LgPjKeJkF2SdNo1IkpQ5Dfl9HCZEVw9OP3CPuEI=
github.com/aws/aws-sdk-go v1.49.17/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.21 h1:Rl8KW6HqkwzhATwvXhyr7vD4JFUMi7oXGAw9SrxxIFY=
github.com/aws/aws-sdk-go v1.49.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.50.5 h1:H2Aadcgwr7a2aqS6ZwcE+l1mA6ZrTseYCvjw2QLmxIA=
github.com/aws/aws-sdk-go v1.50.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.50.6 h1:FaXvNwHG3Ri1paUEW16Ahk9zLVqSAdqa1M3phjZR35Q=
github.com/aws/aws-sdk-go v1.50.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.50.9 h1:yX66aKnEtRc/uNV/1EH8CudRT5aLwVwcSwTBphuVPt8=
github.com/aws/aws-sdk-go v1.50.9/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.12.14 h1:FpgWcv1aqU3xXbMVwEBr2sCeRT1Cctwqg/sWMI4wLoo=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.12.14/go.mod h1:J2zgl/oFM9OWQoaEATWvh426859hrB1cuVEqLgGpi+Q=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.12.16 h1:KZvXflfyoL43jhDe2tDHPeK9C+edHJl2Rb07N7Dq3qY=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.12.16/go.mod h1:SdkjT6MneWbTztIxA5cZ8QTvD4ASCeM7IhUkIIhvVa0=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZGttNXrzRUVtFvp2Ak/Vo=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.27.0 h1:e/HPLjLas04wKnmCUSSXD44cYdVjT/Dcd9CkmlYNyNU=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.27.0/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.18.7 h1:srShyROqxzC7p18Ws8mqM2sqxJO/8L3Kpiqf+NboJLg=
Expand All @@ -54,8 +30,6 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
Expand Down
51 changes: 46 additions & 5 deletions internal/ddb/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,48 @@ import (
)

type Client struct {
ddb *dynamodb.DynamoDB
tableName string
ddb *dynamodb.DynamoDB
tableName string
changesTableName string
}

func (c *Client) PutChanges(ctx context.Context, data any, update shared.Update) error {
changesItem, err := dynamodbattribute.MarshalMap(map[string]interface{}{
"uid": update.Uid,
"applied": update.Applied,
"author": update.Author,
"type": update.Type,
"change": update.Changes,
})

item, err := dynamodbattribute.MarshalMap(data)
if err != nil {
return err
}

transactInput := &dynamodb.TransactWriteItemsInput{
TransactItems: []*dynamodb.TransactWriteItem{
// write the LPA to the deeds table
&dynamodb.TransactWriteItem{
Put: &dynamodb.Put{
TableName: aws.String(c.tableName),
Item: item,
},
},

// record the change
&dynamodb.TransactWriteItem{
Put: &dynamodb.Put{
TableName: aws.String(c.changesTableName),
Item: changesItem,
},
},
},
}

_, err = c.ddb.TransactWriteItemsWithContext(ctx, transactInput)

return err
}

func (c *Client) Put(ctx context.Context, data any) error {
Expand Down Expand Up @@ -54,13 +94,14 @@ func (c *Client) Get(ctx context.Context, uid string) (shared.Lpa, error) {
return lpa, err
}

func New(endpoint, tableName string) *Client {
func New(endpoint, tableName string, changesTableName string) *Client {
sess := session.Must(session.NewSession())
sess.Config.Endpoint = &endpoint

c := &Client{
ddb: dynamodb.New(sess),
tableName: tableName,
ddb: dynamodb.New(sess),
tableName: tableName,
changesTableName: changesTableName,
}

xray.AWS(c.ddb.Client)
Expand Down
25 changes: 13 additions & 12 deletions internal/shared/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ var validIssuers []string = []string{
mrlpa,
}

type lpaStoreClaims struct {
type LpaStoreClaims struct {
jwt.RegisteredClaims
}

// note that default validation for RegisteredClaims checks exp is in the future
func (l lpaStoreClaims) Validate() error {
func (l LpaStoreClaims) Validate() error {
// validate issued at (iat)
iat, err := l.GetIssuedAt()
if err != nil {
Expand Down Expand Up @@ -92,36 +92,37 @@ var bearerRegexp = regexp.MustCompile("^Bearer[ ]+")

// verify JWT from event header
// returns true if verified, false otherwise
func (v JWTVerifier) VerifyHeader(event events.APIGatewayProxyRequest) bool {
func (v JWTVerifier) VerifyHeader(event events.APIGatewayProxyRequest) (*LpaStoreClaims, error) {
jwtHeaders := GetEventHeader("X-Jwt-Authorization", event)

if len(jwtHeaders) < 1 {
return false
return nil, fmt.Errorf("Invalid X-Jwt-Authorization header")
}

tokenStr := bearerRegexp.ReplaceAllString(jwtHeaders[0], "")
if v.verifyToken(tokenStr) != nil {
return false
claims, err := v.verifyToken(tokenStr)
if err != nil {
return nil, err
}

return true
return claims, nil
}

// tokenStr is the JWT token, minus any "Bearer: " prefix
func (v JWTVerifier) verifyToken(tokenStr string) error {
lsc := lpaStoreClaims{}
func (v JWTVerifier) verifyToken(tokenStr string) (*LpaStoreClaims, error) {
lsc := LpaStoreClaims{}

parsedToken, err := jwt.ParseWithClaims(tokenStr, &lsc, func(token *jwt.Token) (interface{}, error) {
return v.secretKey, nil
})

if err != nil {
return err
return nil, err
}

if !parsedToken.Valid {
return fmt.Errorf("Invalid JWT")
return nil, fmt.Errorf("Invalid JWT")
}

return nil
return &lsc, nil
}
Loading

0 comments on commit 1517085

Please sign in to comment.