Skip to content

Commit

Permalink
Fix: Export declared types in the package (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
karrui committed Jun 1, 2020
1 parent 2dbce2d commit d4700ba
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 60 deletions.
15 changes: 14 additions & 1 deletion src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import nacl from 'tweetnacl'
import { encode as encodeBase64, decode as decodeBase64 } from '@stablelib/base64'
import {
encode as encodeBase64,
decode as decodeBase64,
} from '@stablelib/base64'
import { encode as encodeUTF8, decode as decodeUTF8 } from '@stablelib/utf8'

import {
DecryptParams,
DecryptedContent,
EncryptedContent,
EncryptedFileContent,
FormField,
Keypair,
PackageInitParams,
} from './types'
import { getPublicKey } from './util/publicKey'
import { determineIsFormFields } from './util/validate'

Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import webhooks from './webhooks'
import crypto from './crypto'
import { PackageInitParams } from './types'
import verification from './verification'
import webhooks from './webhooks'
/**
* Entrypoint into the FormSG SDK
* @param {Object} options
Expand Down
28 changes: 14 additions & 14 deletions src/typings/index.d.ts → src/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
type PackageInitParams = {
export type PackageInitParams = {
mode?: PackageMode
webhookSecretKey?: string
verificationOptions?: VerificationOptions
}

// A field type available in FormSG as a string
type FieldType =
export type FieldType =
| 'section'
| 'radiobutton'
| 'dropdown'
Expand All @@ -24,7 +24,7 @@ type FieldType =
| 'mobile'

// Represents form field responses in a form.
type FormField = {
export type FormField = {
_id: string
question: string
fieldType: FieldType
Expand All @@ -39,57 +39,57 @@ type FormField = {
// nonce and encrypted data in base-64.
// A string in the format of
// <SubmissionPublicKey>;<Base64Nonce>:<Base64EncryptedData>
type EncryptedContent = string
export type EncryptedContent = string

interface DecryptParams {
export interface DecryptParams {
encryptedContent: EncryptedContent
version: number
verifiedContent?: EncryptedContent
}

type DecryptedContent = {
export type DecryptedContent = {
responses: FormField[]
verified?: Record<string, any>
}

type EncryptedFileContent = {
export type EncryptedFileContent = {
submissionPublicKey: string
nonce: string
binary: Uint8Array
}

// A base-64 encoded cryptographic keypair suitable for curve25519.
type Keypair = {
export type Keypair = {
publicKey: string
secretKey: string
}

type PackageMode = 'staging' | 'production' | 'development' | 'test'
export type PackageMode = 'staging' | 'production' | 'development' | 'test'

type VerificationOptions = {
export type VerificationOptions = {
secretKey?: string
transactionExpiry?: number
}

// A verified answer contains a field ID and answer
type VerifiedAnswer = {
export type VerifiedAnswer = {
fieldId: string
answer: string
}

// Add the transaction ID and form ID to a VerifiedAnswer to obtain a signature
type VerificationSignatureOptions = VerifiedAnswer & {
export type VerificationSignatureOptions = VerifiedAnswer & {
transactionId: string
formId: string
}

// Creating a basestring requires the epoch in addition to signature requirements
type VerificationBasestringOptions = VerificationSignatureOptions & {
export type VerificationBasestringOptions = VerificationSignatureOptions & {
time: number
}

// Authenticate a VerifiedAnswer with a signatureString and epoch
type VerificationAuthenticateOptions = VerifiedAnswer & {
export type VerificationAuthenticateOptions = VerifiedAnswer & {
signatureString: string
submissionCreatedAt: number
}
2 changes: 2 additions & 0 deletions src/util/publicKey.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { SIGNING_KEYS } from '../resource/signing-keys'
import { PackageMode } from '../types'

import STAGE from './stage'

/**
Expand Down
2 changes: 2 additions & 0 deletions src/util/stage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PackageMode } from '../types'

const STAGE: { [stage in PackageMode]: stage } = {
staging: 'staging',
production: 'production',
Expand Down
2 changes: 2 additions & 0 deletions src/util/validate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FormField } from '../types'

function determineIsFormFields(tbd: any): tbd is FormField[] {
if (!Array.isArray(tbd)) {
return false
Expand Down
66 changes: 46 additions & 20 deletions src/verification/authenticate.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,75 @@
import nacl from 'tweetnacl'
import { encode as encodeUTF8 } from '@stablelib/utf8'
import { decode as decodeBase64 } from '@stablelib/base64'

import { VerificationAuthenticateOptions } from '../types'
import basestring from './basestring'

export default function ( publicKey: string, transactionExpirySeconds: number ): Function {
export default function (
publicKey: string,
transactionExpirySeconds: number
): Function {
/*
* Checks if signature was made within TRANSACTION_EXPIRE_AFTER_SECONDS before submission was created
* @param {Number} signatureTime ms
* @param {Number} submissionCreatedAt ms
*/
function isSignatureTimeValid(signatureTime: number, submissionCreatedAt: number): boolean {
* Checks if signature was made within TRANSACTION_EXPIRE_AFTER_SECONDS before submission was created
* @param {Number} signatureTime ms
* @param {Number} submissionCreatedAt ms
*/
function isSignatureTimeValid(
signatureTime: number,
submissionCreatedAt: number
): boolean {
const maxTime = submissionCreatedAt
const minTime = maxTime - transactionExpirySeconds * 1000
return signatureTime > minTime && signatureTime < maxTime
}

/**
* Verifies signature
* @param {object} data
* @param {string} data.signatureString
* @param {number} data.submissionCreatedAt date in milliseconds
* @param {string} data.fieldId
* @param {string} data.answer
* @param {string} data.publicKey
*/
function authenticate({ signatureString, submissionCreatedAt, fieldId, answer }: VerificationAuthenticateOptions) {
* Verifies signature
* @param {object} data
* @param {string} data.signatureString
* @param {number} data.submissionCreatedAt date in milliseconds
* @param {string} data.fieldId
* @param {string} data.answer
* @param {string} data.publicKey
*/
function authenticate({
signatureString,
submissionCreatedAt,
fieldId,
answer,
}: VerificationAuthenticateOptions) {
try {
const { v: transactionId, t: time, f: formId, s: signature } = signatureString
const {
v: transactionId,
t: time,
f: formId,
s: signature,
} = signatureString
.split(',')
.reduce(function (acc: {[key: string]: string}, v: string){
.reduce(function (acc: { [key: string]: string }, v: string) {
const i = v.indexOf('=')
acc[v.substring(0, i)] = v.substring(i + 1)
return acc
}, {})

const signatureDate = +time
if (isSignatureTimeValid(signatureDate, submissionCreatedAt)) {
const data = basestring({ transactionId, formId, fieldId, answer, time: signatureDate })
const data = basestring({
transactionId,
formId,
fieldId,
answer,
time: signatureDate,
})
return nacl.sign.detached.verify(
encodeUTF8(data),
decodeBase64(signature),
decodeBase64(publicKey)
)
} else {
console.info(`Signature was expired for signatureString="${signatureString}" signatureDate="${signatureDate}" submissionCreatedAt="${submissionCreatedAt}"`)
console.info(
`Signature was expired for signatureString="${signatureString}" signatureDate="${signatureDate}" submissionCreatedAt="${submissionCreatedAt}"`
)
return false
}
} catch (error) {
Expand Down
15 changes: 10 additions & 5 deletions src/verification/basestring.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { VerificationBasestringOptions } from '../types'

/**
* Formats given data into a string for signing
*/
function basestring(
{ transactionId, formId, fieldId, answer, time }: VerificationBasestringOptions
): string {
function basestring({
transactionId,
formId,
fieldId,
answer,
time,
}: VerificationBasestringOptions): string {
return `${transactionId}.${formId}.${fieldId}.${answer}.${time}`
}
export default basestring

export default basestring
7 changes: 6 additions & 1 deletion src/verification/generate-signature.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import nacl from 'tweetnacl'
import { encode as encodeUTF8 } from '@stablelib/utf8'
import { encode as encodeBase64, decode as decodeBase64 } from '@stablelib/base64'
import {
encode as encodeBase64,
decode as decodeBase64,
} from '@stablelib/base64'

import { VerificationSignatureOptions } from '../types'
import basestring from './basestring'

export default function (privateKey: string) {
Expand Down
15 changes: 8 additions & 7 deletions src/verification/get-public-key.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { VERIFICATION_KEYS } from '../resource/verification-keys'
import { PackageMode } from '../types'
import STAGE from '../util/stage'

/**
Expand All @@ -8,13 +9,13 @@ import STAGE from '../util/stage'
*/
function getPublicKey(mode?: PackageMode) {
switch (mode) {
case STAGE.development:
case STAGE.staging:
return VERIFICATION_KEYS.staging.publicKey
case STAGE.test:
return VERIFICATION_KEYS.test.publicKey
default:
return VERIFICATION_KEYS.production.publicKey
case STAGE.development:
case STAGE.staging:
return VERIFICATION_KEYS.staging.publicKey
case STAGE.test:
return VERIFICATION_KEYS.test.publicKey
default:
return VERIFICATION_KEYS.production.publicKey
}
}

Expand Down
35 changes: 24 additions & 11 deletions src/verification/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* @file Manages verification of otp form fields (email, sms, whatsapp)
* @author Jean Tan
* @author Jean Tan
*/
import { PackageInitParams } from '../types'

import authenticate from './authenticate'
import generateSignature from './generate-signature'
Expand All @@ -13,18 +14,30 @@ import getPublicKey from './get-public-key'
*/
export = function (params: PackageInitParams = {}) {
const { mode, verificationOptions } = params
if(verificationOptions !== undefined){
if (verificationOptions !== undefined) {
const verificationPublicKey = getPublicKey(mode)
const { secretKey: verificationSecretKey, transactionExpiry } = verificationOptions
return {
authenticate: transactionExpiry !== undefined ?
authenticate(verificationPublicKey, transactionExpiry)
: function (){ throw new Error('Provide transactionExpiry when initializing the formsg sdk to use this function.') },
generateSignature: verificationSecretKey !== undefined ?
generateSignature(verificationSecretKey)
: function (){ throw new Error('Provide verificationSecretKey when initializing the formsg sdk to use this function.') },
const {
secretKey: verificationSecretKey,
transactionExpiry,
} = verificationOptions
return {
authenticate:
transactionExpiry !== undefined
? authenticate(verificationPublicKey, transactionExpiry)
: function () {
throw new Error(
'Provide transactionExpiry when initializing the formsg sdk to use this function.'
)
},
generateSignature:
verificationSecretKey !== undefined
? generateSignature(verificationSecretKey)
: function () {
throw new Error(
'Provide verificationSecretKey when initializing the formsg sdk to use this function.'
)
},
}
}
return {}
}

1 change: 1 addition & 0 deletions src/webhooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as url from 'url'

import { PackageInitParams } from './types'
import { sign, verify } from './util/signature'
import { getPublicKey } from './util/publicKey'
import { parseSignatureHeader } from './util/parser'
Expand Down

0 comments on commit d4700ba

Please sign in to comment.