edge-core-js
Version:
Edge account & wallet management library
442 lines (376 loc) • 12.4 kB
text/typescript
import type { Cleaner } from 'cleaners'
import {
asArray,
asBoolean,
asCodec,
asDate,
asEither,
asNumber,
asObject,
asOptional,
asString,
asUnknown,
asValue,
uncleaner
} from 'cleaners'
import { base16, base32, base64 } from 'rfc4648'
import type {
ChallengeErrorPayload,
ChangeOtpPayload,
ChangePasswordPayload,
ChangePin2IdPayload,
ChangePin2Payload,
ChangeRecovery2IdPayload,
ChangeRecovery2Payload,
ChangeSecretPayload,
ChangeUsernamePayload,
ChangeVouchersPayload,
CreateChallengePayload,
CreateKeysPayload,
CreateLoginPayload,
EdgeBox,
EdgeKeyBox,
EdgeLobbyReply,
EdgeLobbyRequest,
EdgeSnrp,
LobbyPayload,
LoginPayload,
LoginRequestBody,
LoginResponseBody,
MessagesPayload,
OtpErrorPayload,
OtpResetPayload,
PasswordErrorPayload,
Recovery2InfoPayload,
UsernameInfoPayload
} from './server-types'
import type { EdgePendingVoucher } from './types'
/**
* A string of hex-encoded binary data.
*/
export const asBase16: Cleaner<Uint8Array> = asCodec(
raw => base16.parse(asString(raw)),
clean => base16.stringify(clean).toLowerCase()
)
/**
* A string of base32-encoded binary data.
*/
export const asBase32: Cleaner<Uint8Array> = asCodec(
raw => base32.parse(asString(raw), { loose: true }),
clean => base32.stringify(clean, { pad: false })
)
/**
* A string of base64-encoded binary data.
*/
export const asBase64: Cleaner<Uint8Array> = asCodec(
raw => base64.parse(asString(raw)),
clean => base64.stringify(clean)
)
// ---------------------------------------------------------------------
// public Edge types
// ---------------------------------------------------------------------
export const asEdgePendingVoucher: Cleaner<EdgePendingVoucher> = asObject({
voucherId: asString,
activates: asDate,
created: asDate,
ip: asString,
ipDescription: asString,
deviceDescription: asOptional(asString)
})
// ---------------------------------------------------------------------
// internal Edge types
// ---------------------------------------------------------------------
export const asEdgeBox: Cleaner<EdgeBox> = asObject({
encryptionType: asNumber,
data_base64: asBase64,
iv_hex: asBase16
})
export const asEdgeKeyBox: Cleaner<EdgeKeyBox> = asObject({
created: asOptional(asDate),
data_base64: asBase64,
encryptionType: asNumber,
iv_hex: asBase16
})
export const asEdgeSnrp: Cleaner<EdgeSnrp> = asObject({
salt_hex: asBase16,
n: asNumber,
r: asNumber,
p: asNumber
})
export const asEdgeLobbyRequest: Cleaner<EdgeLobbyRequest> = asObject({
loginRequest: asOptional(asObject({ appId: asString }).withRest),
publicKey: asBase64,
timeout: asOptional(asNumber)
}).withRest
export const asEdgeLobbyReply: Cleaner<EdgeLobbyReply> = asObject({
publicKey: asBase64,
box: asEdgeBox
})
/**
* An array of base64-encoded hashed recovery answers.
*/
export const asRecovery2Auth: Cleaner<Uint8Array[]> = asArray(asBase64)
// ---------------------------------------------------------------------
// top-level request & response bodies
// ---------------------------------------------------------------------
export const asLoginRequestBody: Cleaner<LoginRequestBody> = asObject({
// The request payload:
data: asUnknown,
// Common fields for all login methods:
challengeId: asOptional(asString),
deviceDescription: asOptional(asString),
otp: asOptional(asString),
syncToken: asOptional(asString),
voucherId: asOptional(asString),
voucherAuth: asOptional(asBase64),
// Secret-key login:
loginId: asOptional(asBase64),
loginAuth: asOptional(asBase64),
// Password login:
userId: asOptional(asBase64),
passwordAuth: asOptional(asBase64),
// PIN login:
pin2Id: asOptional(asBase64),
pin2Auth: asOptional(asBase64),
// Recovery login:
recovery2Id: asOptional(asBase64),
recovery2Auth: asOptional(asRecovery2Auth),
// Messages:
loginIds: asOptional(asArray(asBase64)),
// OTP reset:
otpResetAuth: asOptional(asString),
// Legacy:
did: asOptional(asString),
l1: asOptional(asBase64),
lp1: asOptional(asBase64),
lpin1: asOptional(asBase64),
lra1: asOptional(asBase64),
recoveryAuth: asOptional(asBase64) // lra1
})
export const asLoginResponseBody: Cleaner<LoginResponseBody> = asObject({
// The response payload:
results: asOptional(asUnknown),
// What type of response is this (success or failure)?:
status_code: asNumber,
message: asString
})
// ---------------------------------------------------------------------
// request payloads
// ---------------------------------------------------------------------
export const asChangeOtpPayload: Cleaner<ChangeOtpPayload> = asObject({
otpTimeout: asOptional(asNumber, 7 * 24 * 60 * 60), // seconds
otpKey: asBase32
})
export const asChangePasswordPayload: Cleaner<ChangePasswordPayload> = asObject(
{
passwordAuth: asBase64,
passwordAuthBox: asEdgeBox,
passwordAuthSnrp: asEdgeSnrp,
passwordBox: asEdgeBox,
passwordKeySnrp: asEdgeSnrp
}
)
export const asChangePin2IdPayload: Cleaner<ChangePin2IdPayload> = asObject({
pin2Id: asBase64
})
export const asChangePin2Payload: Cleaner<ChangePin2Payload> = asObject({
pin2Id: asOptional(asBase64),
pin2Auth: asOptional(asBase64),
pin2Box: asOptional(asEdgeBox),
pin2KeyBox: asOptional(asEdgeBox),
pin2TextBox: asEdgeBox
})
export const asChangeRecovery2IdPayload: Cleaner<ChangeRecovery2IdPayload> =
asObject({
recovery2Id: asBase64
})
export const asChangeRecovery2Payload: Cleaner<ChangeRecovery2Payload> =
asObject({
recovery2Id: asBase64,
recovery2Auth: asRecovery2Auth,
recovery2Box: asEdgeBox,
recovery2KeyBox: asEdgeBox,
question2Box: asEdgeBox
})
export const asChangeSecretPayload: Cleaner<ChangeSecretPayload> = asObject({
loginAuthBox: asEdgeBox,
loginAuth: asBase64
})
export const asChangeUsernamePayload: Cleaner<ChangeUsernamePayload> = asObject(
{
userId: asBase64,
userTextBox: asEdgeBox
}
)
export const asChangeVouchersPayload: Cleaner<ChangeVouchersPayload> = asObject(
{
approvedVouchers: asOptional(asArray(asString)),
rejectedVouchers: asOptional(asArray(asString))
}
)
export const asCreateKeysPayload: Cleaner<CreateKeysPayload> = asObject({
keyBoxes: asArray(asEdgeBox),
newSyncKeys: asOptional(asArray(asString), () => [])
})
export const asCreateLoginPayload: Cleaner<CreateLoginPayload> = asObject({
appId: asString,
loginId: asBase64,
parentBox: asOptional(asEdgeBox)
})
// ---------------------------------------------------------------------
// response payloads
// ---------------------------------------------------------------------
export const asChallengeErrorPayload: Cleaner<ChallengeErrorPayload> = asObject(
{
challengeId: asString,
challengeUri: asString
}
)
export const asCreateChallengePayload: Cleaner<CreateChallengePayload> =
asObject({
challengeId: asString,
challengeUri: asOptional(asString)
})
export const asLobbyPayload: Cleaner<LobbyPayload> = asObject({
request: asEdgeLobbyRequest,
replies: asArray(asEdgeLobbyReply)
})
const asTrue = asValue(true)
export const asLoginPayload: Cleaner<LoginPayload> = asObject({
// Identity:
appId: asString,
created: asDate,
loginId: asBase64,
syncToken: asOptional(asString),
// Nested logins:
children: asOptional(asArray(raw => asLoginPayload(raw))),
parentBox: asOptional(asEdgeBox),
// 2-factor login:
otpKey: asOptional(asEither(asTrue, asBase32)),
otpResetDate: asOptional(asDate),
otpTimeout: asOptional(asNumber),
// Password login:
passwordAuthBox: asOptional(asEdgeBox),
passwordAuthSnrp: asOptional(asEdgeSnrp),
passwordBox: asOptional(asEither(asTrue, asEdgeBox)),
passwordKeySnrp: asOptional(asEdgeSnrp),
// PIN v2 login:
pin2Box: asOptional(asEither(asTrue, asEdgeBox)),
pin2KeyBox: asOptional(asEdgeBox),
pin2TextBox: asOptional(asEdgeBox),
// Recovery v2 login:
question2Box: asOptional(asEdgeBox),
recovery2Box: asOptional(asEither(asTrue, asEdgeBox)),
recovery2KeyBox: asOptional(asEdgeBox),
// Secret-key login:
loginAuthBox: asOptional(asEdgeBox),
// Username:
userId: asOptional(asBase64),
userTextBox: asOptional(asEdgeBox),
// Voucher login:
pendingVouchers: asOptional(asArray(asEdgePendingVoucher), () => []),
// Resources:
keyBoxes: asOptional(asArray(asEdgeKeyBox)),
mnemonicBox: asOptional(asEdgeBox),
rootKeyBox: asOptional(asEdgeBox),
syncKeyBox: asOptional(asEdgeBox)
})
export const asMessagesPayload: Cleaner<MessagesPayload> = asArray(
asObject({
loginId: asBase64,
otpResetPending: asOptional(asBoolean, false),
pendingVouchers: asOptional(asArray(asEdgePendingVoucher), () => []),
recovery2Corrupt: asOptional(asBoolean, false)
})
)
export const asOtpErrorPayload: Cleaner<OtpErrorPayload> = asObject({
login_id: asOptional(asBase64),
otp_reset_auth: asOptional(asString),
otp_timeout_date: asOptional(asDate),
reason: asOptional(asValue('ip', 'otp'), 'otp'),
voucher_activates: asOptional(asDate),
voucher_auth: asOptional(asBase64),
voucher_id: asOptional(asString)
})
export const asOtpResetPayload: Cleaner<OtpResetPayload> = asObject({
otpResetDate: asDate
})
export const asPasswordErrorPayload: Cleaner<PasswordErrorPayload> = asObject({
wait_seconds: asOptional(asNumber)
})
export const asRecovery2InfoPayload: Cleaner<Recovery2InfoPayload> = asObject({
question2Box: asEdgeBox
})
export const asUsernameInfoPayload: Cleaner<UsernameInfoPayload> = asObject({
loginId: asBase64,
// Password login:
passwordAuthSnrp: asOptional(asEdgeSnrp),
// Recovery v1 login:
questionBox: asOptional(asEdgeBox),
questionKeySnrp: asOptional(asEdgeSnrp),
recoveryAuthSnrp: asOptional(asEdgeSnrp)
})
// ---------------------------------------------------------------------
// uncleaners
// ---------------------------------------------------------------------
// Common types:
export const wasEdgeBox = uncleaner<EdgeBox>(asEdgeBox)
export const wasEdgeLobbyReply = uncleaner<EdgeLobbyReply>(asEdgeLobbyReply)
export const wasEdgeLobbyRequest =
uncleaner<EdgeLobbyRequest>(asEdgeLobbyRequest)
// Top-level request / response bodies:
export const wasLoginRequestBody =
uncleaner<LoginRequestBody>(asLoginRequestBody)
export const wasLoginResponseBody =
uncleaner<LoginResponseBody>(asLoginResponseBody)
// Request payloads:
export const wasChangeOtpPayload =
uncleaner<ChangeOtpPayload>(asChangeOtpPayload)
export const wasChangePasswordPayload = uncleaner<ChangePasswordPayload>(
asChangePasswordPayload
)
export const wasChangePin2IdPayload = uncleaner<ChangePin2IdPayload>(
asChangePin2IdPayload
)
export const wasChangePin2Payload =
uncleaner<ChangePin2Payload>(asChangePin2Payload)
export const wasChangeRecovery2IdPayload = uncleaner<ChangeRecovery2IdPayload>(
asChangeRecovery2IdPayload
)
export const wasChangeRecovery2Payload = uncleaner<ChangeRecovery2Payload>(
asChangeRecovery2Payload
)
export const wasChangeSecretPayload = uncleaner<ChangeSecretPayload>(
asChangeSecretPayload
)
export const wasChangeUsernamePayload = uncleaner<ChangeUsernamePayload>(
asChangeUsernamePayload
)
export const wasChangeVouchersPayload = uncleaner<ChangeVouchersPayload>(
asChangeVouchersPayload
)
export const wasCreateKeysPayload =
uncleaner<CreateKeysPayload>(asCreateKeysPayload)
export const wasCreateLoginPayload =
uncleaner<CreateLoginPayload>(asCreateLoginPayload)
// Response payloads:
export const wasChallengeErrorPayload = uncleaner<ChallengeErrorPayload>(
asChallengeErrorPayload
)
export const wasCreateChallengePayload = uncleaner<CreateChallengePayload>(
asCreateChallengePayload
)
export const wasLobbyPayload = uncleaner<LobbyPayload>(asLobbyPayload)
export const wasLoginPayload = uncleaner<LoginPayload>(asLoginPayload)
export const wasMessagesPayload = uncleaner<MessagesPayload>(asMessagesPayload)
export const wasOtpErrorPayload = uncleaner<OtpErrorPayload>(asOtpErrorPayload)
export const wasOtpResetPayload = uncleaner<OtpResetPayload>(asOtpResetPayload)
export const wasPasswordErrorPayload = uncleaner<PasswordErrorPayload>(
asPasswordErrorPayload
)
export const wasRecovery2InfoPayload = uncleaner<Recovery2InfoPayload>(
asRecovery2InfoPayload
)
export const wasUsernameInfoPayload = uncleaner<UsernameInfoPayload>(
asUsernameInfoPayload
)