8.2.1 • Published 4 years ago

@mylens/lens-api v8.2.1

Weekly downloads
60
License
ISC
Repository
-
Last release
4 years ago

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 support@mylens.io 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: 1) 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 
    });

2) 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. 
}

3) 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. 1) 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. 
    });

2) 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); 

2) 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/lens-api@3.1.1/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
}
PropertyDefaultTypeDescription
redirectUrlREQUIREDstringThe 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 LensRequestTargetA 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}'stringThe 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.
dataREQUIREDArray\<LensRequestData>An array of LensRequestData objects, which denote what information the user's lens needs to contain to properly satisfy your request.
stateundefinedstringThis 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,
}
PropertyDefaultTypeDescription
typeREQUIRED'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.
idgeneratedstringAllows 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'.
publicKeygeneratedstringAllows 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>
}
PropertyDefaultTypeDescription
nameREQUIREDstring
type'string'string
labelnamestring
valueundefinedstring
displaytype dependent
validatorsundefinedArray\<string or Validator>
rows5integer
mintype dependentnumber
maxtype dependentnumber
optionsundefinedArray\<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:

TypeDescription
stringThis is the most basic type that can be stored. This will accept any valid values that are strings
string/email-addressThis type will validate for an email address, e.g. mike@mylens.io.
string/phone-numberThis type will validate any phone number that follows this format: "+15051234567."
string/imageThis 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-timeA valid date-time string. Conforms to Javascript new Date().toLocaleString().
string/dateA valid date string. Conforms to Javascript: new Date(aValue[0], aValue[1]-1, aValue[2]).toLocaleDateString()
string/timeA type that represents a valid time format.
numberA type that respresents any valid number format, including floating point.
number/integerA type that can only be a valid integer.
booleanA true or false type.
compositeThis 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>
}
PropertyDefaultTypeDescription
typeREQUIRED'required', 'regex', 'options', 'length' or 'range'
labelundefinedstring
testundefinedstring
minundefinednumber
maxundefinednumber
optionsundefinedArray\<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
}
PropertyDefaultTypeDescription
valueREQUIREDstring or number or boolean
labelvaluestring
8.2.1

4 years ago

8.2.0

4 years ago

8.1.0

4 years ago

8.0.0

4 years ago

7.2.1

4 years ago

7.2.0

4 years ago

7.1.0

4 years ago

7.0.0

4 years ago

6.2.2

4 years ago

6.2.1

4 years ago

6.2.0

4 years ago

6.1.2

4 years ago

6.1.3

4 years ago

6.1.0

4 years ago

6.1.1

4 years ago

6.0.0

4 years ago

5.2.0

4 years ago

5.1.0

4 years ago

5.0.0

4 years ago

4.1.3

5 years ago

4.1.2

5 years ago

4.1.1

5 years ago

4.1.0

5 years ago

4.0.0

5 years ago

3.2.0

5 years ago

3.1.3

5 years ago

3.1.2

5 years ago

3.1.1

5 years ago

3.1.0

5 years ago

3.0.0

5 years ago

2.0.0

5 years ago

1.1.0

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago