Skip to content

Commit

Permalink
Merge pull request #73 from aahna-ashina/dw-58/send-template-updates-to
Browse files Browse the repository at this point in the history
Send template updates to Apple Wallet passes (#6)
  • Loading branch information
aahna-ashina authored Sep 13, 2022
2 parents 9d96e96 + 1032a01 commit b5aedc3
Show file tree
Hide file tree
Showing 38 changed files with 1,686 additions and 1,241 deletions.
4 changes: 4 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

<!--- Please describe in detail how you tested your changes. -->

- [ ] Status checks pass
- [ ] Works on Goerli
- [ ] Works on Mainnet

## Are There Admin Tasks?

<!--- Please include any related admin tasks, like adding/changing environment variables in Vercel. -->
34 changes: 34 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build and Lint

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest

defaults:
run:
working-directory: server

strategy:
matrix:
node-version: [16.x, 18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: server/package-lock.json
- run: npm install

- run: npm run build
- run: npm run lint
34 changes: 34 additions & 0 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Integration Tests

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest

defaults:
run:
working-directory: server

strategy:
matrix:
node-version: [16.x, 18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: server/package-lock.json
- run: npm install

- run: cp .env.local.goerli .env.local
- run: npm run e2e:headless
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI
name: Unit Tests

on:
push:
Expand Down Expand Up @@ -32,10 +29,7 @@ jobs:
cache: 'npm'
cache-dependency-path: server/package-lock.json
- run: npm install
- run: npm run build
- run: npm run lint

- run: npm run test
- run: npm run test:coverage
- uses: codecov/codecov-action@v3
- run: cp .env.local.goerli .env.local
- run: npm run e2e:headless
2 changes: 1 addition & 1 deletion server/.env.local.goerli
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ GOOGLE_WALLET_CLASS_ID=
GOOGLE_WALLET_USER_ID=

APPLE_AUTH_TOKEN_HMAC_SEED=df54xDNd653eV9c3f89Y
APPLE_WEB_SERVICE_URL=https://mobile-passport-aahna.vercel.app/api/apple
APPLE_WEB_SERVICE_URL=https://mobile-passport-goerli.vercel.app/api/apple
APPLE_CA_CERTIFICATE_PEM=-----BEGIN CERTIFICATE-----MIICkzCCAfwCCQCReZn54qszRzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB05hdGlvbjMxEDAOBgNVBAcMB05hdGlvbjMxEDAOBgNVBAoMB05hdGlvbjMxEDAOBgNVBAsMB05hdGlvbjMxFDASBgNVBAMMC25hdGlvbjMub3JnMR8wHQYJKoZIhvcNAQkBFhBtYWlsQG5hdGlvbjMub3JnMCAXDTIyMDgzMTA5MTU0MFoYDzIwNTAwMTE2MDkxNTQwWjCBjDELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB05hdGlvbjMxEDAOBgNVBAcMB05hdGlvbjMxEDAOBgNVBAoMB05hdGlvbjMxEDAOBgNVBAsMB05hdGlvbjMxFDASBgNVBAMMC25hdGlvbjMub3JnMR8wHQYJKoZIhvcNAQkBFhBtYWlsQG5hdGlvbjMub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQSaR1x0ZbrejVDhL/GZjcYBEBZOB9sZd+iNoP39eXFRqDAFLDHaUUPY39cWWa/Lo+51CSiGAsrFXAslwzi/EQ0zZslCd3GZbBY1J2F5Wx6INXJCvye+hwOjXYMzC7C/HWI2tNodyYpjJoj+tCmDvXo/khl2ScvQdTWCFwBaMPWwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFUOFQUNiA7xDheo3mQPTSbxL7f6W8SD3g2Sz10wtWOtwChMeo6q+QpWEr/RxfLXY8uUKHO70JtZM4y/4caeZkxv7bvzfQTU9Uv6jdZbv7Hm4G/A4o1sI2h/qmCErh55wI39eEgylkMZRNF7FTBgoZ3Eig+aafjCY/AaR2/5rhMP-----END CERTIFICATE-----
APPLE_CERTIFICATE_PEM=MIICijCCAfMCAhI0MA0GCSqGSIb3DQEBBAUAMIGMMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTmF0aW9uMzEQMA4GA1UEBwwHTmF0aW9uMzEQMA4GA1UECgwHTmF0aW9uMzEQMA4GA1UECwwHTmF0aW9uMzEUMBIGA1UEAwwLbmF0aW9uMy5vcmcxHzAdBgkqhkiG9w0BCQEWEG1haWxAbmF0aW9uMy5vcmcwHhcNMjIwODMxMDkyMzEzWhcNMjMwODMxMDkyMzEzWjCBjDELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB05hdGlvbjMxEDAOBgNVBAcMB05hdGlvbjMxEDAOBgNVBAoMB05hdGlvbjMxEDAOBgNVBAsMB05hdGlvbjMxFDASBgNVBAMMC25hdGlvbjMub3JnMR8wHQYJKoZIhvcNAQkBFhBtYWlsQG5hdGlvbjMub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaXTn53tBPs/rtFeCLW1Rkm/lo7H1+W0aPbgL3qI9HL06btmen0T+VX4zmY3gia4uSWXJmlmtv72wxqGgxyNBe7pjQlrK+y5S7ZuiBu6CNxtJkmm7EGN3QwNops1OM6mYFJqWLc/Knemgng3MAeK+D+7k/1tMyBSmrRam58D30WQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAMwiX1XYSMUMhTqcschL7x41fFtjzTGVEKlCYhZzCgLtgLrR1WeG5JW5QVx97B1llbuL6Up/yYLGSJ1tGgFamI7aQZyLbw/iDIcTF/C/d0inkPNokIYcczzYP7wtoYkhtq9+dR5HXbg0Kf+ObGNMCcfon/WpfSyk2wGpwhmDRDhA
APPLE_CERTIFICATE_KEY=MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANpdOfne0E+z+u0V4ItbVGSb+WjsfX5bRo9uAveoj0cvTpu2Z6fRP5VfjOZjeCJri5JZcmaWa2/vbDGoaDHI0F7umNCWsr7LlLtm6IG7oI3G0mSabsQY3dDA2imzU4zqZgUmpYtz8qd6aCeDcwB4r4P7uT/W0zIFKatFqbnwPfRZAgMBAAECgYA0AuSeSZ4gfeQUuJNFXjmZxTUA8uNpR2BlJXT7fGC0OeZlXGsQI90bPSkYbzTcLfWOpFOFb+qjPMnugghfY0+N/BiN1WNedex6+jeq/tcMvUUnWSHdcEoJcdwsSLCT8b+mdA5rjEbkFedLYJ8omhzSeD9osf6HhxcmuXZ5tM35yQJBAP+vDaGW/hBdEi4MlR0sS4cfQx8VAuhFHN/d9oV6duWuNw+TcKT8OpKlxStXzWCC9Qg14f0dQzfW2iGG5VqLBd8CQQDaolu0o0ABQv6vpfMBq/30fgSt3+2QpFnQKlBKd8JwoowzZSjVz3yJoowym46Ev/Mi2ETd9FurdpqvFnqHeBzHAkAgn4tTtNpR6C4rpftYr5Are3eq/ZlTXY4jmkScH1YQVDw+Roe25V/r2i7gKuHPkKYHGCjvjM5Iz6chY/7boKVtAkBIu35ah3yBbvIfKMvAoKDpNwsDdN0pIyOlto3jWBfdNJYDewGsr0u01dhh9ZHh46FU1DGEzQe58Mjguk+kxXZ9AkAF1rglqJAnYtUGECz+9T+iWl0P3OR33O/DHz2DRBPP5Snt7t3kaJxTTTBinj3ng4BZ3KiGZP9Vp/iT8YqAxL9D
Expand Down
9 changes: 9 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ Run unit tests:
npm run test
```

## Code Coverage

[![codecov](https://codecov.io/gh/nation3/mobile-passport/branch/main/graph/badge.svg)](https://codecov.io/gh/nation3/mobile-passport)

Run code coverage:
```
npm run test:coverage
```

## Add the file with local environment variables

```
Expand Down
18 changes: 18 additions & 0 deletions server/TEMPLATE_UPDATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Template Update

This document explains, step-by-step, how to update the template of a pass.

## Apple Templates

1. Copy the the current template version to a new folder, e.g. `cp -r template-versions/apple/2 template-versions/apple/3`
1. Commit the changes
1. Make your changes in the new folder
1. Commit the changes
1. Increase the `appleTemplateVersion` in `utils/Config.ts`
1. Test that the new pass is successfully generated by making a `[GET]` request to `http://localhost:3000/api/downloadPass` (see full request example in `cypress/e2e/api/apple/downloadPass.cy.ts`)
1. Test that it works to open the generated `.pkpass` file on an iOS device
1. Commit the changes

## Google Templates

`// TO DO`
51 changes: 51 additions & 0 deletions server/cypress/e2e/api/apple/[passTypeIdentifier].cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
describe('Get the List of Updatable Passes', () => {

it('error when wrong request method (POST instead of GET)', () => {
cy.request({
method: 'POST',
url: '/api/apple/v1/devices/b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(400)
expect(JSON.stringify(response.body)).to.contain('Request Not Authorized: Wrong request method')
})
})

it('204 when unknown deviceLibraryIdentifier', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/devices/cypress_b00e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(204)
})
})

it('200 when existing deviceLibraryIdentifier', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/devices/cypress_b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)
expect(JSON.stringify(response.body)).to.contain('serialNumbers')
// TODO: verify serial number value(s)
expect(JSON.stringify(response.body)).to.contain('lastUpdated')
})
})

it('200 when existing deviceLibraryIdentifier and passesUpdatedSince=1662541136', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/devices/cypress_b33e3a3dccb3030333e3333da33333a3/registrations/pass.org.passport.nation3?passesUpdatedSince=1662541136',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)
expect(JSON.stringify(response.body)).to.contain('serialNumbers')
// TODO: verify serial number value(s)
expect(JSON.stringify(response.body)).to.contain('lastUpdated')
})
})
})

export {}
4 changes: 2 additions & 2 deletions server/cypress/e2e/api/apple/[serialNumber].cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ describe('Register a Pass for Update Notifications', () => {
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401)
expect(JSON.stringify(response.body)).to.contain('Request Not Authorized: duplicate key value violates unique constraint')
expect(response.status).to.eq(200)
expect(JSON.stringify(response.body)).to.contain('Serial Number Already Registered for Device')
})
})

Expand Down
32 changes: 32 additions & 0 deletions server/cypress/e2e/api/apple/log.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
describe('Log a Message', () => {

it('error when wrong request method (GET instead of POST)', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/log',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401)
expect(JSON.stringify(response.body)).to.contain('Request Not Authorized: Wrong request method')
})
})

it('success when POST request', () => {
cy.request({
method: 'POST',
url: '/api/apple/v1/log',
body: {
logs: [
'[2022-09-06 09:14:07 +0800] Get serial #s task (for device b33e3a3dccb3030333e3333da33333a3, pass type pass.org.passport.nation3, last updated (null); with web service url https://passports.nation3.org/api/apple) encountered error: Unexpected response code 404',
'[2022-09-06 09:14:07 +0800] Get serial #s task (for device b33e3a3dccb3030333e3333da33333a3, pass type pass.org.passport.nation3, last updated (null); with web service url https://passports.nation3.org/api/apple) encountered error: Unexpected response code 404'
]
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)
expect(JSON.stringify(response.body)).to.contain('OK')
})
})
})

export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import crypto from 'crypto'

describe('Send an Updated Pass', () => {

it('error when wrong request method (POST instead of GET)', () => {
cy.request({
method: 'POST',
url: '/api/apple/v1/passes/pass.org.passport.nation3/5',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401)
expect(JSON.stringify(response.body)).to.contain('Request Not Authorized: Wrong request method')
})
})

it('error when authentication token missing in header', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/passes/pass.org.passport.nation3/5',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401)
expect(JSON.stringify(response.body)).to.contain('Request Not Authorized: Missing/empty header: Authorization')
})
})

/**
* A passport with ID 5 is expected to have been downloaded previously (see downloadPass.cy.ts).
*/
it('success when valid authentication token in header', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/passes/pass.org.passport.nation3/5',
headers: {
'Authorization': 'ApplePass 0x3fbeb3ae33af3fb33f3d33333303d333a333aff33f3133efbc3330333adb333a'
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)
expect(response.headers).to.include({
'content-type': 'application/vnd.apple.pkpass'
})
})
})
})

it('error when serial number missing from `downloads` table', () => {
cy.request({
method: 'GET',
url: '/api/apple/v1/passes/pass.org.passport.nation3/55555',
headers: {
'Authorization': 'ApplePass 0x3fbeb3ae33af3fb33f3d33333303d333a333aff33f3133efbc3330333adb333a'
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(500)
expect(JSON.stringify(response.body)).to.contain('multiple (or no) rows returned')
})
})

export {}
16 changes: 11 additions & 5 deletions server/cypress/e2e/api/downloadPass.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('/api/downloadPass', () => {
expect(response.status).to.eq(400)
expect(JSON.stringify(response.body)).to.contain('Invalid address')
})
}),
})

it('error when invalid address', () => {
cy.request({
Expand All @@ -18,7 +18,7 @@ describe('/api/downloadPass', () => {
expect(response.status).to.eq(400)
expect(JSON.stringify(response.body)).to.contain('Invalid address')
})
}),
})

it('error when missing signature', () => {
cy.request({
Expand All @@ -28,7 +28,7 @@ describe('/api/downloadPass', () => {
expect(response.status).to.eq(400)
expect(JSON.stringify(response.body)).to.contain('Invalid signature')
})
}),
})

it('error when invalid signature', () => {
cy.request({
Expand All @@ -38,15 +38,21 @@ describe('/api/downloadPass', () => {
expect(response.status).to.eq(400)
expect(JSON.stringify(response.body)).to.contain('Invalid signature')
})
}),
})

/**
* The address 0x394b00B5De4E6f30292aCaC37f810Dd0672E211E is the holder of a passport with ID 5:
* https://goerli.etherscan.io/token/0x51f728c58697aff9582cfde3cbd00ec83e9ae7fc?a=0x394b00b5de4e6f30292acac37f810dd0672e211e
*/
it('success when valid address and signature', () => {
cy.request({
url: '/api/downloadPass?address=0x394b00B5De4E6f30292aCaC37f810Dd0672E211E&signature=0xeec065511291b0f3294a8f20a67bffd1a9ad11f0d44d68e8a324c91402ed0dd26fd27400af82325ec82d6fe5cdc0b167f1be0e18dd22d0e4865015608c050d7e1c',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)
// expect(JSON.stringify(response.body)).to.contain('Invalid signature')
expect(response.headers).to.include({
'content-type': 'application/vnd.apple.pkpass'
})
})
})
})
Expand Down
11 changes: 11 additions & 0 deletions server/db-migrations/0.7.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
create type platform as enum ('Apple', 'Google');
create table downloads (
id bigint generated by default as identity primary key,
time_of_download timestamp without time zone default now() not null,
platform platform not null,
template_version int not null,
passport_id bigint not null,
issue_date timestamp without time zone not null,
address text not null,
ens_name text
);
2 changes: 2 additions & 0 deletions server/db-migrations/0.8.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
delete from registrations;
alter table registrations add column template_version int not null;
7 changes: 7 additions & 0 deletions server/db-migrations/0.9.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
create table latest_updates (
id bigint generated by default as identity primary key,
time timestamp without time zone default now() not null,
title text not null,
content text not null
);
insert into latest_updates (title, content) values ('N3GOV-16: All governance proposals must explain how they help advance the North Star metrics', 'This is very self-descriptive, but it''s a nice add-on to Proposal: Set Nation3''s North Star metrics. We have seen too many times how DAOs end up with endless bureaucracy because there''s no room for execution amidst chaos and uncertainty. Therefore having a unified set of metrics to advance, and making all governance proposals adhere to advancing them, makes sense. This is the pull request to the governance proposals repo, and this is the specific change.')
2 changes: 1 addition & 1 deletion server/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export interface GooglePass extends Pass {

export enum Platform {
Apple,
Google,
Google
}
2 changes: 1 addition & 1 deletion server/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module.exports = {
testEnvironment: 'node',
coverageThreshold: {
global: {
lines: 18.55
lines: 80.00
}
}
}
Loading

1 comment on commit b5aedc3

@vercel
Copy link

@vercel vercel bot commented on b5aedc3 Sep 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.