README
Affinity Core SDK - Affinity network DID solution
Table of contents
- Workflow schemas:
- How to install
- Initialize
- Interface
How to install
npm i --save @affinityproject/wallet-core-sdk
Initialize
If you want to specify issuer's URL, pass it in the options.
You can also specify the stack environment to be used in env
variable.
env
- (optional) is enum which can be dev
| staging
| prod
(staging
is used by default).
You should register your entity at (Affinity)[https://...], as result you will get apiKey, which should pass
at options as required parameter there.
const options = {
issuerUrl: 'https://affinity-issuer.staging.affinity-project.org',
apiKey: '....'
}
const commonNetworkMember = new CommonNetworkMember(password, encryptedSeed, options)
options
- (optional) if not defined, values posted above will be used.
Issuer / Holder / Verifier interface examples
Initialize from seed
const options = {
issuerUrl: 'https://affinity-issuer.staging.affinity-project.org',
apiKey: '....'
}
CommonNetworkMember.fromSeed(seedHex, options, password)
options
- (optional) if not defined, values posted above will be used.
password
- (optional) if not defined, it will be generated.
Sign DID document
const signedDidDocument = await commonNetworkMember.signDidDocument(didDocument)
Interface
const { CommonNetworkMember } = require('@affinityproject/wallet-core-sdk')
Generate seed
Generates random seed from which keys could be derived.
const seed = await CommonNetworkMember.generateSeed(didMethod)
didMethod
- (optional) enum (elem
| jolo
), if not defined, elem
DID method will be used.
Create DID
const options = { issuerUrl: 'https://affinity-issuer.staging.affinity-project.org', apiKey: '....' }
const { did, encryptedSeed } = await CommonNetworkMember.register(password, options)
URL mentioned above will be used, if options was not provided.
Creation of DID flow is the same for each member. E.g. creation of DID for Verifier works the same:
const { did, encryptedSeed } = await CommonNetworkMember.register(password)
Intiate SDK
const options = {
issuerUrl: 'https://affinity-issuer.staging.affinity-project.org',
}
const networkMember = new Wallet(password, encryptedSeed, options)
options
- (optional) if not defined, values posted above will be used.
Get DID
const networkMember = new CommonNetworkMember(password, encryptedSeed, options)
const did = networkMember.did
did
- user's DID
Passwordless sign in or sign up if user does not exist + DID creation
NOTE: This passwordless method was designed for a specific usecase, and its
simple user interface achieved by making extra calls to AWS Cognito.
If this method is chosen for authentication you may see failed requests
to Cognito in the browser's console - that is expected behaviour,
exceptions are caught and handled properly.
const options = { env: 'staging' }
const token = await CommonNetworkMember.signIn(username, options, messageParameters)
username
- email or phoneNumber, of existing Cognito user or if it
does not exist, a new one will be created.
options
- (optional) used to specify environment stack dev | staging | prod
.
messageParameters
- (optional) used to specify message, htmlMessage, subject, see signup method.
Confirm sign in
const options = { env: 'staging', didMethod: 'elem' }
const { isNew, commonNetworkMember } = await CommonNetworkMember.confirmSignIn(token, confirmationCode, options)
token
- from previous step.
confirmationCode
- 6 digits code, generated and sent by AWS Cognito/SES.
options
- (optional) used to specify:
env
- environment stack dev | staging | prod
,
didMethod
- DID method elem
| jolo
, if not defined, elem
DID method will be used.
Returns isNew
flag, identifying whether new account was created, and
initialized instance of SDK - commonNetworkMember
.
Sign up
We STRONGLY recommend using a password at least 8 characters, but it's allowed to be 6 min (in this case - salt as username hash and special character will be added on signup, and the same rule will be applied on login cases)
const token = await CommonNetworkMember.signUp(username, password, options, messageParameters)
username
- arbitrary username, email, phone number (+123456789). In case of
arbitrary username, password recovery is not possible.
password
- optional.
Requirements: min length 8, require number, upper and lowercase letter.
NOTE: password is optional if username is email or phone number only.
If not provided, user will be able to login with passwordless flow only
(passwordlessLogin
+ completeLoginChallenge
, with OTP submit).
When username is arbitrary username, password must be provided.
options
- (optional) used to specify environment stack (dev | staging | prod).
By default staging
environment is used.
messageParameters
- optional
const htmlMessage = `
<table align="center" border="1" cellpadding="0" cellspacing="0" width="600">
<tr>
<td bgcolor="#70bbd9">
here is your {{CODE}}.
</td>
</tr>
</table>
`
const messageParameters = {
message: 'Welcome to Affinity, your OTP: {{CODE}}'
subject?: 'Your verification Code'
htmlMessage?
}
{{CODE}} - will be replaced at the message by OTP
If htmlMessage not provided, meesage parameter will be used
To finish registration:
NOTE: email/SMS with verification code (OTP) will be sent to the provided email/phoneNumber, unless username is an arbitrary username.
const options = { env: 'staging', didMethod: 'elem' }
const commonNetworkMember = await CommonNetworkMember.confirmSignUp(token, confirmationCode, options)
token
- token from the previous step (value returned from the signUp
).
confirmationCode
- OTP sent by AWS Cognito/SES. Parameter is required if
email/phoneNumber was given as a username, and is ignored in case when
username is an arbitrary username.
options
- (optional) used to specify:
env
- environment stack dev | staging | prod
,
didMethod
- DID method elem
| jolo
, if not defined, elem
DID method will be used.
To re-send sign up confirmation code (in case when username is email/phoneNumber):
await CommonNetworkMember.resendSignUpConfirmationCode(username, options)
username
- email/phoneNumber.
options
- (optional) used to specify environment stack (dev | staging | prod).
Sign up with email/phoneNumber (example)
const username = 'great_user@email.com'
const password = 'Password123'
const options = { env: 'dev' }
const token = await CommonNetworkMember.signUp(username, password, options)
// OTP is sent out by Cognito
const confirmationCode = '123456' // OTP from email/SMS
const networkMember = await CommonNetworkMember.confirmSignUp(token, confirmationCode, options)
Now user can login
Sign up with arbitrary username (example)
const username = 'great_user'
const password = 'Password123'
const options = { env: 'dev' }
const networkMember = await CommonNetworkMember.signUp(username, password, options)
Now user can login with username and pasword
Sign up to Affinity Wallet with already created DID/keys. (Create User at Affinity Wallet and store there user keys)
User already have created keys in advance, e.g.
const { did, encryptedSeed } = await CommonNetworkMember.register(password, options)
Sign up with already created keys:
const keyParams = { encryptedSeed, password }
const username = 'example@affinity-project.org'
const userPassword = 'Password123'
const options = { env: 'dev' }
const messageParameters = { message: 'Welcome to Affinity, here is your OTP: {{CODE}}' } // (optional)
const token = await CommonNetworkMember.signUpWithExistsEntity(keyParams, username, userPassword, options, messageParameters)
If username arbitrary value (not email or phoneNumber), then signUpWithExistsEntity
will go throw all signup flow
In case when phoneNumber or email was used, need to execute confirm signup method with recieved OTP:
const affinityWallet = await CommonNetworkMember.confirmSignUpWithExistsEntity(keyParams, token, confirmationCode, options)
Update Did Document (supported only for jolo method):
init SDK
const affinityWallet = new CommonNetworkMember(password, encryptedSeed)
// OR
const affinityWallet = await CommonNetworkMember.fromLoginAndPassword(userName, userPassword, options)
Then
await affinityWallet.updateDidDocument(didDocument)
where didDocument - its valid signed didDocument
Store keys on Affinity Guardian Wallet:
NOTE: storeEncryptedSeed
is called as a part of registration flow.
await commonNetworkMember.storeEncryptedSeed(username, password, token)
username
- username
password
- password
token
- Cognito access token, required for authorization. If not provided,
in order to get it, sign in to Cognito will be initiated.
Get Signup Credential
const signedCredential = await CommonNetworkMember.getSignupCredentials(accessToken, idToken, options)
accessToken
- Access JWT from AWS cognito
idToken
- ID JWT from AWS cognito
options
- (optional) used to specify environment stack (dev | staging | prod).
Credential offer is sent from wallet backend back to client if idToken contains verified email or phone number. Client automatically accepts and requests a signed credential. Signed credential is sent back. Signed credential is saved in the user vault.
Initiate instance of SDK with login and pasword
To initiate instance of networkMember using just login and password (when user already signed up at Affinity, and if stored his keys at Affinity Guardian Wallet).
const networkMember = await CommonNetworkMember.fromLoginAndPassword(username, password, options)
options
- (optional) used to specify environment stack (dev | staging | prod).
Passwordless login
Login to the network by username, registered in AWS Cognito.
const token = await CommonNetworkMember.passwordlessLogin(username, options)
username
- email or phone number, at which confirmation code will be sent.
options
- (optional) used to specify environment stack (dev | staging | prod).
Complete login challenge and initiate instance of SDK:
await CommonNetworkMember.completeLoginChallenge(token, confirmationCode, options)
token
- token from the previous step.
confirmationCode
- 6 digits code, generated and sent by AWS Cognito/SES.
options
- (optional) if not defined defaults will be used.
Password recovery
NOTE: Password recovery is not possible with arbitrary username.
await CommonNetworkMember.forgotPassword(username, options)
username
- email or phone number, at which confirmation code will be sent.
options
- (optional) used to specify environment stack (dev | staging | prod).
Complete change password challenge:
await CommonNetworkMember.forgotPasswordSubmit(username, confirmationCode, newPassword, options)
username
- username in Cognito.
confirmationCode
- 6 digits code, generated and sent by AWS Cognito/SES.
newPassword
- new password for Cognito user.
options
- (optional) used to specify environment stack (dev | staging | prod).
Change username
const commonNetworkMember = new CommonNetworkMember(password, encryptedSeed, options)
await commonNetworkMember.changeUsername(newUsername)
newUsername
- email or phone number that will be a new username in Cognito.
Complete change username challenge:
const commonNetworkMember = new CommonNetworkMember(password, encryptedSeed, options)
await commonNetworkMember.confirmChangeUsername(newUsername, confirmationCode, options)
newUsername
- username from the previous step.
confirmationCode
- 6 digits code, generated and sent by AWS Cognito/SES.
Sign Out
Signs out current user.
await commonNetworkMember.signOut(options)
options
- (optional) used to specify environment stack (dev | staging | prod).
Issuer
Initiate credential offer request
const credentialOfferToken = await issuer.generateCredentialOfferRequestToken(offeredCredentials, options)
const { audienceDid, expiresAt, nonce, callbackUrl } = options
audienceDid
(string) - audience of genreated token.
expiresAt
(isoString) - expires of genreated token.
nonce
(string) - nonce/jti of genreated token.
callbackUrl
(string)
Initiate DID auth
const authDidRequestToken = await issuer.generateDidAuthRequest(options)
const { audienceDid, expiresAt, nonce, callbackUrl } = options
audienceDid
(string) - audience of genreated token.
expiresAt
(isoString) - expires of genreated token.
nonce
(string) - nonce/jti of genreated token.
callbackUrl
(string)
const offeredCredentials = [
{
type: 'IssuerCustomCredential',
renderInfo,
},
]
renderInfo
(optional) where issuer can define how that VC can be represented/shown.
Example:
const renderInfo = {
logo: {
url: 'https://miro.medium.com/fit/c/240/240/1*jbb5WdcAvaY1uVdCjX1XVg.png',
},
background: {
url: 'https://i.imgur.com/0Mrldei.png',
},
text: {
color: '#05050d',
},
}
credentialOfferToken
can be passed to the wallet side, and let Wallet/Holder option to response
to this offer if usser want take offered credentials.
Validate Holder Response on Offer Request
const { isValid, did, nonce, selectedCredentials } = await issuer.verifyCredentialOfferResponseToken(
credentialOfferResponseToken,
credentialOfferRequestToken,
)
credentialOfferRequestToken
(optional) - using when need check response against request (nonce, audience).
Validates response token and verify signature, if verification not passed response { isValid: false }
if response is valid returns also { issuer, nonce, selectedCredentials }
.
did
- it's DID which signed that response.
Sign multiple credentials
import { VCV1Unsigned } from '@affinityproject/vc-common'
import { VCSPhonePersonV1, getVCPhonePersonV1Context } from '@affinityproject/vc-data'
import { buildVCV1Unsigned, buildVCV1Skeleton } from '@affinityproject/issuer-util'
const unsignedCredentials: VCV1Unsigned[] = [
buildVCV1Unsigned({
skeleton: buildVCV1Skeleton<VCSPhonePersonV1>({
id: 'urn:urn-5:...',
credentialSubject: {
data: {
'@type': ['Person', 'PersonE', 'PhonePerson'],
telephone: '+1 555 555 5555',
},
},
holder: { id: 'did:...:...' },
type: 'PhoneCredentialPersonV1',
context: getVCPhonePersonV1Context(),
}),
issuanceDate: new Date().toISOString(),
expirationDate: new Date(new Date().getTime() + 10 * 60 * 1000).toISOString(),
}),
]
const credentials = await signCredentials(credentialOfferResponseToken, unsignedCredentials)
credentialOfferResponseToken
- credential offer response JWT.
credentialParams
- array of params for credentials, where expiresAt
is optional.
Generate signed credential
import { VCSPhonePersonV1, getVCPhonePersonV1Context } from '@affinityproject/vc-data'
const credentialSubject: VCSPhonePersonV1 = {
data: {
'@type': ['Person', 'PersonE', 'PhonePerson'],
telephone: '+1 555 555 5555',
},
}
const credentialMetadata = {
context: [getVCPhonePersonV1Context()],
name: 'Phone Number',
type: ['PhoneCredentialPersonV1'],
}
const credential = await issuer.signCredential(
credentialSubject,
credentialMetadata,
{ credentialOfferResponseToken, requesterDid },
expiresAt,
)
credentialSubject
- data which should be present in VC according to VC schema, must be a valid VCV1Subject
.
credentialMetadata
- schema of credential (should be defined and Issuer and Verifier use the same,
so verifier will be able to understand what kind of credential was created by Issuer).
Verifier
Initiate credential share request
const credentialShareRequestToken = await verifier.generatePresentationChallenge(
credentialRequirements,
issuerDid,
jwtOptions,
)
const { audienceDid, expiresAt, nonce, callbackUrl } = jwtOptions
audienceDid
(string) - audience of genreated token.
expiresAt
(isoString) - expires of genreated token.
nonce
(number) - nonce/jti of genreated token.
callbackUrl
(string)
Generates JWT with info of which VC credentialRequirements
to be provided from Wallet/Holder.
const credentialRequirements = [{ type: ['Credential', 'ProofOfNameCredential'] }]
callbackUrl
- (optional) Holder/Wallet will be able send response on this request to this URL.
issuerDid
- (optional) its contrain, that define required isser of VC.
credentialShareRequestToken
can be send to Wallet/Holder to anwser on this with response with requested VC inside.
Validate Holder Response on Share Request
const { isValid, did, nonce, suppliedCredentials } = await verifier.verifyCredentialShareResponseToken(
credentialShareResponseToken,
credentialShareRequestToken,
shouldOwn,
)
credentialShareResponseToken
- (optional) using when need check response against request (when request have constrains).
shouldOwn
- (optional) Verify that subject is holder of VC. Default true as per W3C spec.
Its validate response token and verify signature on provided VC inside, if verification not passed response { isValid: false }
.
If response is valid it returns also { did, nonce, suppliedCredentials }
.
Validate Holder Response on Did auth Request
const { isValid, did, nonce } = await verifier.verifyDidAuthResponse(authDidResponseToken, authDidRequestToken)
Its validate response token, if verification not passed response { isValid: false }
if response is valid returns also { did, nonce }
Wallet
Create Response on credential share request
const responseToken = await wallet.createPresentationFromChallenge(
credentialShareRequestToken,
suppliedCredentials,
callbackURL,
)
suppliedCredentials
- credentials which Holder providing for Verifier.
callbackURL
- (optional)
Create Response on credential offer request
const responseToken = await wallet.createCredentialOfferResponseToken(credentialOfferRequestToken)
Agree to recieve proposed credentials by the Issuer.
Create Response on DID auth request
const authDidResponseToken = await wallet.createDidAuthResponse(authDidRequestToken)
Delete All Credentials
Warning: calling this endpoint will remove all credentials from the wallet
await wallet.deleteAllCredentials()