@pluginlab/node-sdk
v0.6.0
Published
A package to manage PluginLab authentication and more for your users.
Downloads
89
Readme
PluginLab Admin Node SDK
Installation
npm i -S @pluginlab/node-sdk
Getting started
To get started you have to init the PluginLabApp on your backend.
You will need your pluginId and the secret you can generate from your PluginLab dashboard.
import { PluginLabApp } from "@pluginlab/node-sdk";
const app = new PluginLabApp({
pluginId: '<THE_PLUGIN_ID>',
secretKey: '<THE_SECRET>',
});
// The PluginLabAuth is the main entry point for all the members related operations
const auth = await app.getAuth();
Verify the user token
Everytime ChatGPT will talk to your backend through PluginLab proxy, it will send the user token in the Authorization
header.
You can verify this token using the verifyIdToken
method.
Note: verifyIdToken returns the entire payload since version v0.2.0. Before v0.2.0 only the uid was returned.
// If the token is valid, it will return the user id
const payload = await auth.verifyIdToken('TOKEN_FROM_HEADER')
try {
// If it's invalid it will throw an error
const wrong = await auth.verifyIdToken('wrong');
} catch (e) {
console.error('Error', e);
}
The returned payload matches the PluginLabTokenPayload
interface.
export interface TokenPayloadUser {
id: string;
email: string;
name: string | null;
givenName: string | null;
planId: string | null;
priceId: string | null;
}
export interface PluginLabTokenPayload {
uid: string;
aud: string;
iss: string;
iat: number;
exp: number;
user: TokenPayloadUser;
}
Get a member by id or email
const memberById = await auth.getMemberById(member.id);
console.log('Member by id', memberById);
const memberByEmail = await auth.getMemberByEmail(member.auth.email);
console.log('Member by email', memberByEmail);
// Member example:
// {
// metadata: { updated: 'metadata' },
// auth: {
// isVerified: true,
// hasPassword: false,
// providerUserId: '104060273339127949239',
// email: '[email protected]',
// signInMethod: 'google'
// },
// createdAtMs: 1685804986789,
// customFields: {},
// givenName: 'Kevin',
// pictureUrl: 'https://somepicture.com',
// familyName: 'Piacentini',
// id: 'mem_39a23f618be79391a31387d2c3d61967e83f7010',
// email: '[email protected]',
// name: 'Kevin Piacentini',
// updatedAtMs: 1685827381733
// }
Note: some fields such as givenName, name, familyName, pictureUrl are available only if authenticated with an OAuth provider that provides these info, such as Google.
List members
You can list your members using the getMembers
method.
const numberPerPage = 10;
const { items, total, nextPageToken } = await auth.getMembers(numberPerPage, null);
Get member's identities
When a member logs in using a third-party auth provider, the latter generates an access token and optionally a refresh token that the application can use to interact with the third-party service on behalf of the user.
PluginLab allows you to get the identities that have been generated when the user logged in:
const identitites = await auth.getMemberIdentities('mem_fd9a1ba5e385e3d97412cdfbd7b8284c4c038e18')
if (identities.google) {
console.log(`User has a google identity attached! Access token is: ${google.accessToken}`)
}
if (identities.github) {
//
}
// applies to each third-party provider supported by PluginLab - see typings for more info
In this example identities will be a type of Record<IdentityProvider>, ProviderIdentityData>
.
export type IdentityProvider = 'google' | 'gitlab' | 'microsoft' | 'github';
export interface IdentityProviderData {
provider: IdentityProvider;
providerUserId: string;
lastUsedAtMs: number | null;
accessTokenExpiresAtMs: number | null;
accessToken: string | null;
refreshTokenExpiresAtMs: number | null;
refreshToken: string | null;
}
Refresh identity access token
For OAuth providers such as Google or Gitlab the access token has a limited lifetime. PluginLab provides an endpoint in case your token needs to be refreshed.
The following endpoint will refresh the token
const refreshedIdentity = await auth.refreshIdentityToken('<MEMBER_ID>', 'google');
console.log('Fresh access token', refreshedIdentity.accessToken);
The provider id can be either google
or gitlab
at this moment.
This endpoint will return a Promise<IdentityProviderData>
.
|| Note this endpoint currently works only with google and gitlab. Github does not provide any refresh token at this moment. If you need support for more providers feel free to reach out.
Handling refresh errors
Sometimes it's possible that the refresh token is revoked or expired. In that case PluginLab will not be able to refresh the token anymore. In that situation, PluginLab will return the following error with a HTTP CODE 422:
try {
const freshIdentity = await auth.refreshMemberIdentityToken('<MEMBER_ID>', 'gitlab');
} catch (e) {
if (e?.errorInfo?.code === 'auth/refresh-token-failed') {
// then return redirect your user to the auth-portal page.
}
}
Create a member
const member = await auth.createMember({
email: '[email protected]',
password: 'some-strong-password',
isVerified: true,
metadata: {
optional: 'any metadata',
something: 'else',
}
});
console.log('Member created', member);
Update a member
// The update acts as a patch.
// So every field is optional.
// Although metadata will be replaced as a whole object if you specify it.
await auth.updateMember(member.id, {
metadata: {
updated: 'metadata',
},
name: 'Kevin',
isVerified: false,
givenName: 'Kevin',
familyName: 'Durand',
pictureUrl: 'https://some-picture.com',
});
Delete a member
await auth.deleteMember(member.id);
console.log('Member deleted');
Webhooks
The webhook utility allows you to verify the signature of a webhook request sent by PluginLab. This is useful to make sure the request is coming from PluginLab and not from a third party.
Basic usage example
To verify the signature you only have to create a new instance of the Webhook class with your secret key.
Then you can use either the verifySignatureOrThrow
or isSignatureValid
methods to verify the signature (depending on the coding style you prefer).
These methods take the body (as a string or as an object) and the signature.
Example in an express server:
import { Webhook, WebhookHeaders, PluginLabWebhookError } from "@pluginlab/node-sdk";
app.post('/webhook-example-1', (req, res) => {
try {
const signature = req.header(WebhookHeaders.Signature);
const webhook = new Webhook('YOUR_WEBHOOK_SECRET');
webhook.verifySignatureOrThrow(req.body, signature)
// Here we know the request has been sent by PluginLab
return res.status(200).json({ message: 'Webhook OK' });
} catch (e) {
if (e instanceof PluginLabWebhookError) {
return res.status(400).json({ code: e.code, message: e.message });
}
return res.status(500).json({ message: 'Internal server error' });
}
})
app.post('/webhook-example-2', (req, res) => {
const hookSignature = req.header(WebhookHeaders.Signature);
const webhook = new Webhook('YOUR_WEBHOOK_SECRET');
const isValid = webhook.isSignatureValid(req.body, hookSignature)
if (!isValid) {
return res.status(400).json({ message: 'Invalid signature' });
}
// Here we know the request has been sent by PluginLab
return res.status(200).json({ message: 'Webhook OK' });
})
Webhook headers
PluginLab sends many headers with the webhook request. The WebhookHeaders object contains all the headers names.
export enum WebhookHeaders {
Signature = 'X-PluginLab-Signature',
Event = 'X-PluginLab-Event',
DeliveryId = 'X-PluginLab-Delivery-Id',
HookId = 'X-PluginLab-Hook-Id',
PluginId = 'X-PluginLab-Plugin-Id',
SignatureVersion = 'X-PluginLab-Signature-Version',
SignatureAlgorithm = 'X-PluginLab-Signature-Algorithm',
PoweredBy = 'X-Powered-By',
Timestamp = 'X-PluginLab-Timestamp',
}
usage example:
import {WebhookHeaders } from '@pluginlab/node-sdk';
const signature = req.header(WebhookHeaders.Signature);
Webhook errors
TheverifySignatureOrThrow
method will throw aPluginLabWebhookError
if the signature is invalid.
ThePluginLabWebhookError
has a code
and a message
that can be the following:
export const MissingSecretError = {
code: 'webhook/missing-secret',
message: 'Missing secret. Please init the Webhook with a secret.',
} as const;
export const MissingBodyError = {
code: 'webhook/missing-body',
message: 'Missing body. Did this event originate from a PluginLab webhook?',
} as const;
export const MissingSignatureError = {
code: 'webhook/missing-signature',
message:
'Missing hook signature. Did this event originate from a PluginLab webhook?',
} as const;
export const InvalidBodyTypeError = {
code: 'webhook/invalid-body-type',
message:
'Invalid body type. The body must be either an object or a string.',
} as const;
export const InvalidSignatureError = {
code: 'webhook/invalid-signature',
message: 'Invalid hook signature.',
} as const;
Monetization
This SDK allows you to access some information related to user monetization.
Get user's current quota usage
If you are using quotas to rate limit and paywall your plugin then you can get the current quota of a given user as shown below:
const monetization = app.getMonetization()
const usage = await monetization.getMemberQuotaUsage('mem_6b8f55542d6b8668f95efda684179bf6555e3624');
The returned usage is an object that fits the following type:
interface MemberQuotaUsage {
interval: string;
isOverLimit: boolean;
isUnlimited: boolean;
resetInDays: number;
usageCount: number;
usageLimit: number;
}
Events
On PluginLab, an event is used as a representaiton of a plugin request and is traditionally generated on each request made by ChatGPT. The SDK offers limited but useful utilities to deal with events.
To get started, you need to leverage the event service by calling the app.getEvent
method.
// assuming app was previously initialized as showed above
const event = app.getEvent();
Create a custom event
If you want to create custom events that will appear in your PluginLab dashboard and count as part of your users' quota, you can rely on the createCustom
method.
This is especially useful if your service is accessible from other means that ChatGPT but you still want to account for usage using PluginLab.
event.createCustom({
// case insensitive, 10 chars. max
eventSource: 'WEB_UI',
// location is optional
location: {
countryCode: 'US',
subdivisionCode: 'CA', // optional
},
// optional, isInQuota is ignored if this is not set
memberId: 'mem_fd9a1ba5e385e3d97412cdfbd7b8284c4c038e18',
// optional, defaults to true and requires member to be set
isInQuota: true
})
Assuming the provided memberId
refers to a valid plugin member then a new event will get created once the above code runs. It will appear in your PluginLab dashboard's event section.