@mylens/lens-api
v8.2.1
Published
Lens request creation, and lens access for 3rd parties
Downloads
8
Readme
Lens API
This repo contains functions and classes to both log in with Lens and to request Lenses from end-users. We accomplish this by leveraging both OAuth2, OpenID Connect, and secure encryption algorithms such as AES256.
Getting started
To be able to subscribe to your customer's data and to use Lens as an identity provider, you must first receive credentials from MyLens Inc. Currently, there is no automated process for this, so if you are interested in using Lens, please send an email to [email protected] with the following information:
Your name
Your email address
The domain name where you want to use Lens
Once we have this information, we will send you some imporant variables that you will need to use the LensAPI, namely:
- CLIENT_ID - the ID we use to identify you and your company
- CLIENT_SECRET - The secret you will use to validate tokens sent from clients. Please store this variable in a safe place!
Lens SSO
Lens SSO is compliant with the OpenID Connect standard. It does not support the implicit or hybrid flow but uses the PKCE flow so that 3rd party applications do not require backend software. Initiating a login is and only requires 3 steps:
- From your front-end, start the login:
import LensApi from '@mylens/lens-api'
const lensApi = new LensApi({
requestUrl: DASHBOARD + '/lens-fulfillment', // This URL generally doesn't change, but it is good to be explicit here. (default: https://vault.mylens.io)
authorizationApiEndpoint: AUTH_SERVER, // Default is set to production auth service.
clientId: CLIENT_ID, // This is the client ID we give you after you send us the email in the getting started section.
});
// Initiate the login process after LensAPI object has been created.
lensApi.beginLoginRequest({
// The redirectURI is the location you want your users to arrive at after they have logged in.
// This value must also be in our database so that we don't have malicious redirects taking place.
// i.e. if you own mybusiness.com and you want to be able to redirect your users to all
// URLs with that domain, you can specify https://mybusiness.com/* in your lms.mylens.io project.
redirectUri: window.location.href
});
- Check to see if a login sequence is currently underway:
if(lensApi.isPendingAuthorizeRequest()) {
// We are arriving here to finish the login!
} else {
// There is no pending login request, so continue as normal.
}
- If there is a pending login request from step 2, we can end it and store the tokens in local storage for further use.
// This call will automatically store your tokens in local storage. Congratulations, the user is now logged in!
const authRequestResult = await lensApi.endAuthorizeRequest();
Once the user is logged in, you can fetch their credentials at any time. If they are expired, you can force them to log in again. We have not yet implemented refresh tokens, so you must begin the login process again for that user should their tokens expire. Here is a quick snippet of how the tokens can be accessed:
/*
*export interface LoginRequestResult {
accessToken: string;
idToken: string;
expires: Date;
scope: Array<ScopeType>;
state?: string;
}
*/
const loginRequest = lensApi.getLoginRequest();
Request a new lens
Requesting a Lens is not much different that performing SSO, the only difference is that when the user is redirected back to your client application, they will now have references to data that they wanted to share with you directly from their data vault. Additionally, all login tokens are refreshed as well after they return from the dashboard site. Lenses and authentication are handled through very similar steps.
- Create the Lens API object:
import LensApi from '@mylens/lens-api'
const lensApi = new LensApi({
requestUrl: DASHBOARD + '/lens-fulfillment', // This URL generally doesn't change, but it is good to be explicit here. (default: https://vault.mylens.io)
authorizationApiEndpoint: AUTH_SERVER, // Default is set to production auth service.
clientId: CLIENT_ID, // This is the client ID we give you after you send us the email in the getting started section.
});
- Issue a Lens request:
let data : Array<LensRequestData> = [
{
name: "firstName", // Name as it is stored
type: 'string', // The type of data that is to be stored.
label: "First Name", // The label that the user sees when they fill out their "First Name"
// This forces the dashboard to treat this data item as New, i.e. it will not try to find other
// data sources named "firstName" and fill this position for the user. The user of course can
// still choose to search for an existing data source, but it will not be automatically selected
// in this case.
preselectMode: LensRequestDataPreselectModeType.New,
value: name,
validators: [
'required'
]
},/* Other data you might want */ ];
// Next we need to construct the request
const request : LensRequest = {
redirectUri: `${APP_DOMAIN}/home`, // URL to redirect to after request has been completed.
target: LensRequestTargetType.General, // Please see below for the differences
label: `${name}'s information`,
data: data
};
// Finally issue the request. This will send the user to the dashboard where they
// can fill out their
lensApi.beginLensRequest(request);
- When the user is redirected back to your client, they will have a Lens fullfillment on the request. From there it is easy to fetch the Lens using the Lens API and using the same interface.
const authRequest = await lensApi.current.endAuthorizeRequest();
// Auth request contains all the same fields as the end login request, except there is also a LensRequest on the
// object as well.
if (authRequest.lensRequest) {
// We have a lens, we can decode it.
} else {
// There was no lens on this authorization
}
import LensAPI from 'lens';
const lensApi = new LensAPI({ clientId: "MY_CLIENT_ID" });
// Will redirect to mylens.io to complete the request, and then back to
// the specified redirectURL
// Will be redirected to (for example) https://vault.mylens.io/lens-fulfillment?clientId=MY_CLIENT_ID&redirectUrl=https://www.mywebsite.com/get-user-information...
lensApi.request({
redirectURL: "https://www.mywebsite.com/get-user-information",
target: LensRequestTargetType.Self, // Optional
data: [{
name: "firstName",
type: "string",
value: "Nathan",
}]
});
// Alternatively, to request a specific, known target, make this call
// instead. Note, in order to access the contents of the returned lens,
// the privateKey corresponding to the publicKey must be known.
lensApi.request({
redirectURL: "https://www.mywebsite.com/get-user-information",
target: {
type: LensRequestTargetType.General,
id: 'someuniqueidforyourapp', // Omit the id if you want a new user + random id generated for you.
publicKey: 'avalidpublickey' // Omit the public key if you want a new public/private key pair generated for this user, assuming they are not already known.
},
data: [{
name: "firstName",
type: "string",
value: "Nathan",
}]
});
Return from requesting a new lens
import LensAPI, { LensRequestResult, LensRef, User } from 'lens';
const lensApi = new LensAPI({ clientId: "MY_CLIENT_ID" });
// As long as we are a valid redirect (which wasn't canceled) this will
// succeed. Otherwise it should throw.
const result: LensRequestResult = lensApi.endRequest();
// Access to these properties may depend on if the request was anonymous
// if accessing clientside.
const lensRef: LensRef = result.lens;
const lensUrl: string = result.lens.url;
// The private key will only be available if a user was returned with the result
const lensPrivateKey: string = result.lens.privateKey;
// A user will only be provided if a new user was created with the lens.
// This will happen if the LensRequestTargetType is 'General', and
// no additional 'id' was provided for the target.
const user: User = result.user;
const userId: string = result.user.id;
const userPublicKey: string = result.user.publicKey;
const userPrivateKey: string = result.user.privateKey;
Obtain information about a lens (serverside, using private key)
import LensAPI, { Lens } from 'lens';
const lensApi = new LensAPI({ clientId: "MY_CLIENT_ID", privateKey: "MY_PRIVATE_KEY" });
const lensUrl: string = "https://hub.blockstack.org/store/15Bg8gbLKYfmJiEsRjdnmo5rUXQLF1Th39/lens-user-shares/47b199cf-99cb-4077-a3d1-2fa85725ad73.json";
const lens: Lens = await lensApi.resolveLens({ url: lensUrl });
const firstName : string = lens.data.filter(d => d.name == 'firstName')[0].value;
Obtain information about an anonymous lens (clientside, using a stored private key)
import LensAPI, { Lens } from 'lens';
const lensApi = new LensAPI({ clientId: "MY_CLIENT_ID" });
const lensUrl: string = "https://hub.blockstack.org/store/15Bg8gbLKYfmJiEsRjdnmo5rUXQLF1Th39/non-lens-user-shares/47b199cf-99cb-4077-a3d1-2fa85725ad73.json";
const lensPrivateKey: string = "3263216305a7021356a2b523fa1e29e908f98124c279d5783ab18f2320e1d4e2"
const lens: Lens = await lensApi.resolveLens({ url: lensUrl, privateKey: lensPrivateKey });
const firstName : string = lens.data.filter(d => d.name == 'firstName')[0].value;
Quickly navigate the data in a lens
/* First resolve the lens, doing the above */
const lens: Lens;
// Get the value of the first match
const firstName: string = lens.get('firstName').value;
// Iterate through a list of emailAddresses
for(let email of lens.get('emailAddress')) {
const email = email.value;
// Do something with it.
}
// Get the first email's value
const firstEmail = lens.get('emailAddress').value
// Get the last email's value
const lastEmail = lens.get('emailAddress').last.value
// Get the list of all data as a LensData, to explore
const lData : LensData = lens.get();
// Get the first item's value
const firstVal = lData.value;
// Get the last one's value.
const lastVal = lData.last.value;
// Get the 3nd data item's value
const thirdVal = lData.next().next().value;
// Or get the 3nd data item's value directly
const thirdVal = lData.index(2).value;
// Get all data results in this LensData
const allData : Array<Data> = lData.results();
// Get an array of LensData for array manipulation.
const allLData : Array<LensData> = lData.toArray();
// Composite sub-data items can be accessed through the
// 'children' property, which returns a LensData.
// Get a LensData for a composite data.
const emergencyContact = lens.get('emergencyContact');
// Get the value of a specific child data item contained in the composite.
const emergencyPhoneNumber = emergencyContact.children.get('phoneNumber').value;
Include a "Share Lens" Button in a webpage that you may not have full control over:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Awesome Site</title>
<!--Include the Lens API!-->
<script src="https://unpkg.com/@mylens/[email protected]/umd/lens-api-bundle.js"></script>
</head>
<body>
<!-- page content -->
<button onclick="shareLens()"> Share Lens </button>
<script>
function shareLens() {
let req = {
// Where to redirect users after they have fulfilled their Lens
redirectUrl: ("https://yoursite.com") + '/user-info',
target: "general",
// Request any key/value pair from your customers.
data: [{
name: "FirstName",
type: "Text",
},
{
name: "FamilyName",
type: "Text",
},
{
name: "EmailAddress",
type: "Text",
},
{
name: "PhoneNumber",
type: "Text"
}]
}
// Create a Lens API object with your client ID.
const api = new window.LensAPI.default({
clientId: 'your-client-id',
requestUrl: "https://vault.mylens.io/lens-fulfillment"
});
// Fire off the request, and be happy that you don't have to worry about data management :-)
api.request(req);
}
</script>
</body>
</html>
Lens Request Definition
LensRequest
When requesting a lens, you submit a LensRequest
object to the LensAPI
's request
function.
LensRequest {
redirectUrl: string,
target: 'self' or 'general' or LensRequestTarget,
label: string,
data: Array<LensRequestData>,
state: string
}
| Property | Default | Type | Description |
| ------ | ------ | ------ | ------ |
| redirectUrl | REQUIRED | string | The URL the user will be returned to, after completing the Lens Request form. The URL must match the redirectUrl
you have on file with the project. |
| target | 'self' | 'self', 'general', or LensRequestTarget | A lens is encrypted for a specific target. Use this field to control who this lens is for. By default, it will be encrypted for the privateKey on file for your clientId
, which is the same as a target of 'self'. Specify 'general' to have a new userId
, publicKey
and privateKey
returned to you. If you have a userId
and publicKey
you would like to use instead, specify a LensRequestTarget here. |
| label | 'Lens Shared with {clientName}' | string | The request label
specifies how this particular lens is stored in the user's dashboard. The user can use this label to quickly identify the lens it has shared through this request. By default it simply denotes the lens has been shared with your project, using the name
on file with your project. |
| data | REQUIRED | Array<LensRequestData> | An array of LensRequestData objects, which denote what information the user's lens needs to contain to properly satisfy your request. |
| state | undefined | string | This optional property is for your use only. The Lens system will not use or modify it. It will be returned to you in the lens request reply just as submitted here. If absent no state is returned. |
LensRequestTarget
An optional LensRequestTarget object may be submitted to exert more control over who the lens is encrypted for.
LensRequestTarget {
type: 'self' or 'general'
id: string,
publicKey: string,
}
| Property | Default | Type | Description |
| ------ | ------ | ------ | ------ |
| type | REQUIRED | 'self' or 'general' | A lens is encrypted for a specific target. Use this field to control who this lens is for. If set to 'self', it will be encrypted for the privateKey on file for your clientId
, and id
and publicKey
are ignored. Specify 'general' to either have a new userId
, publicKey
and privateKey
returned to you, or to use the userId
and/or publicKey
specified. |
| id | generated | string | Allows you to specify a specific target this lens should be stored for. If a 'user' already exists in the owner's storage, that user entry (along with any stored publicKey
) will be used. The id
is only used if type
is set to 'general'. |
| publicKey | generated | string | Allows you to specify the publicKey used to encrypt the lens for your target user. This will only be used if type
is set to 'general', and id
is provided, and does not yet represent an existing user in the owner's storage. |
LensRequestData
To request a single piece of data, you must provide a LensRequestData object in the data
property of a LensRequest.
LensRequestData {
name: string,
type: string,
label: string,
value: string,
display: string,
validators: Array<string or Validator>,
rows: number,
min: number,
max: number,
options: Array<string or number or boolean or Option>
}
| Property | Default | Type | Description | | ------ | ------ | ------ | ------ | | name | REQUIRED | string | | | type | 'string' | string | | | label | name | string | | | value | undefined | string | | | display | type dependent | | | | validators | undefined | Array<string or Validator> | | | rows | 5 | integer | | | min | type dependent | number | | | max | type dependent | number | | | options | undefined | Array<string or number or boolean or Option> | |
LensRequestData type
property
A Lens Request can ask for type specific data to help with validation. The list below illustrates some of the possible types that the user of the Lens API can ask for:
| Type | Description |
| ---- | ------------|
| string | This is the most basic type that can be stored. This will accept any valid values that are strings|
| string/email-address | This type will validate for an email address, e.g. [email protected]
. |
| string/phone-number | This type will validate any phone number that follows this format: "+15051234567."|
| string/image | This type will allow the Lens Requestor to ask the user to populate an image. Ultimately, images are stored as base64 objects in the User's vault. Additional size contraints and can be placed on these as well. |
| string/date-time | A valid date-time string. Conforms to Javascript new Date().toLocaleString()
. |
| string/date | A valid date string. Conforms to Javascript: new Date(aValue[0], aValue[1]-1, aValue[2]).toLocaleDateString()
|
| string/time | A type that represents a valid time format. |
| number | A type that respresents any valid number format, including floating point. |
| number/integer | A type that can only be a valid integer. |
| boolean | A true or false type. |
| composite | This is considered a complex type that allows the aggregation of multiple data fields into a single logical group. This type allows the user of the Lens API the ability to ask for logical categories of information of the user and store them in a single field. Essentially this can be thought of as a Lens to a Lens. |
LensRequestData display
property
Validator
To further restrict what value can be returned for a data item, you can provide validators.
Validator {
type: 'required', 'regex', 'options', 'length' or 'range',
label: string,
test: string,
min: number,
max: number,
options: Array<string or number or boolean or Option>
}
| Property | Default | Type | Description | | ------ | ------ | ------ | ------ | | type | REQUIRED | 'required', 'regex', 'options', 'length' or 'range' | | | label | undefined | string | | | test | undefined | string | | | min | undefined | number | | | max | undefined | number | | | options | undefined | Array<string or number or boolean or Option> | |
Option
Options allow you to restrict input while providing valuable descriptions.
Option {
value: string or number or boolean,
label: string
}
| Property | Default | Type | Description | | ------ | ------ | ------ | ------ | | value | REQUIRED | string or number or boolean | | | label | value | string | |