Skip to content

Commit

Permalink
feat(lastpass): add integrations for lastpass
Browse files Browse the repository at this point in the history
  • Loading branch information
Hassan Wari authored and Hassan Wari committed Dec 17, 2024
1 parent 8096410 commit 665f158
Show file tree
Hide file tree
Showing 14 changed files with 585 additions and 41 deletions.
55 changes: 55 additions & 0 deletions flows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6334,6 +6334,61 @@ integrations:
attributes: object
relationships: object
links: object
lastpass:
actions:
create-user:
description: Creates a user in Lastpass.
output: User
endpoint:
method: POST
path: /users
group: Users
input: LastPassCreateUser
delete-user:
description: Deletes a user in Lastpass.
endpoint:
method: DELETE
path: /users
group: Users
output: SuccessResponse
input: EmailEntity
syncs:
users:
runs: every day
description: |
Fetches a list of users from Lastpass.
output: User
track_deletes: true
sync_type: full
endpoint:
method: GET
path: /users
group: Users
models:
EmailEntity:
email: string
SuccessResponse:
success: boolean
ActionResponseError:
message: string
CreateUser:
firstName: string
lastName: string
email: string
LastPassCreateUser:
firstName: string
lastName: string
email: string
groups?: string[]
duousername?: string
securidusername?: string
password?: string
password_reset_required?: boolean
User:
id: string
firstName: string
lastName: string
email: string
lever:
actions:
create-note:
Expand Down
58 changes: 58 additions & 0 deletions integrations/lastpass/actions/create-user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!-- BEGIN GENERATED CONTENT -->
# Create User

## General Information

- **Description:** Creates a user in Lastpass.
- **Version:** 0.0.1
- **Group:** Others
- **Scopes:** _None_
- **Endpoint Type:** Action
- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/lastpass/actions/create-user.ts)


## Endpoint Reference

### Request Endpoint

`POST /users`

### Request Query Parameters

_No request parameters_

### Request Body

```json
{
"firstName": "<string>",
"lastName": "<string>",
"email": "<string>",
"groups?": [
"<string>"
],
"duousername?": "<string>",
"securidusername?": "<string>",
"password?": "<string>",
"password_reset_required?": "<boolean>"
}
```

### Request Response

```json
{
"id": "<string>",
"firstName": "<string>",
"lastName": "<string>",
"email": "<string>"
}
```

## Changelog

- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/lastpass/actions/create-user.ts)
- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/lastpass/actions/create-user.md)

<!-- END GENERATED CONTENT -->

63 changes: 63 additions & 0 deletions integrations/lastpass/actions/create-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { NangoAction, User, LastPassCreateUser, ProxyConfiguration, ActionResponseError } from '../../models';
import type { LastPassBody, LastPassCreateNewUser, LastPassResponse } from '../types';
import { getCredentials } from '../helpers/get-credentials.js';
import { lastPassCreateUserSchema } from '../../schema.zod.js';

export default async function runAction(nango: NangoAction, input: LastPassCreateUser): Promise<User> {
const parsedInput = lastPassCreateUserSchema.safeParse(input);
if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to create a user: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError<ActionResponseError>({
message: 'Invalid input provided to create a user'
});
}

const createUser: LastPassCreateNewUser = {
username: input.email,
fullname: `${input.firstName} ${input.lastName}`,
...(input.groups && { groups: input.groups }),
...(input.duousername && { duousername: input.duousername }),
...(input.securidusername && { securidusername: input.securidusername }),
...(input.password && { password: input.password }),
...(input.password_reset_required !== undefined && {
password_reset_required: input.password_reset_required
})
};

const credentials = await getCredentials(nango);
const lastPassInput: LastPassBody = {
cid: credentials.cid,
provhash: credentials.provhash,
cmd: 'batchadd',
data: [createUser]
};

const config: ProxyConfiguration = {
// https://support.lastpass.com/s/document-item?language=en_US&bundleId=lastpass&topicId=LastPass/api_add_users.html&_LANG=enus
endpoint: `/enterpriseapi.php`,
data: lastPassInput,
retries: 10
};
const response = await nango.post<LastPassResponse>(config);

const isSuccess = response?.data?.status === 'OK';

// we dont have an Id present in the user's object, so we will use the email as the id
const user: User = {
id: input.email,
firstName: input.firstName,
lastName: input.lastName,
email: input.email
};

if (isSuccess) {
return user;
} else {
const errorMessages = response?.data?.error?.join(', ') || 'Unknown error';
throw new nango.ActionError<ActionResponseError>({
message: `Failed to create user in LastPass: ${errorMessages}`
});
}
}
46 changes: 46 additions & 0 deletions integrations/lastpass/actions/delete-user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!-- BEGIN GENERATED CONTENT -->
# Delete User

## General Information

- **Description:** Deletes a user in Lastpass.
- **Version:** 0.0.1
- **Group:** Others
- **Scopes:** _None_
- **Endpoint Type:** Action
- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/lastpass/actions/delete-user.ts)


## Endpoint Reference

### Request Endpoint

`DELETE /users`

### Request Query Parameters

_No request parameters_

### Request Body

```json
{
"email": "<string>"
}
```

### Request Response

```json
{
"success": "<boolean>"
}
```

## Changelog

- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/lastpass/actions/delete-user.ts)
- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/lastpass/actions/delete-user.md)

<!-- END GENERATED CONTENT -->

35 changes: 35 additions & 0 deletions integrations/lastpass/actions/delete-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { NangoAction, ProxyConfiguration, SuccessResponse, EmailEntity } from '../../models';
import type { LastPassBody } from '../types';
import { getCredentials } from '../helpers/get-credentials.js';

export default async function runAction(nango: NangoAction, input: EmailEntity): Promise<SuccessResponse> {
if (!input.email) {
throw new nango.ActionError({
message: 'Email is required to delete a user'
});
}
const credentials = await getCredentials(nango);
const data: LastPassBody = {
cid: credentials.cid,
provhash: credentials.provhash,
cmd: 'deluser',
data: {
username: input.email,
deleteaction: 2 // Delete user. Deletes the account entirely.
}
};
const config: ProxyConfiguration = {
// https://support.lastpass.com/s/document-item?language=en_US&bundleId=lastpass&topicId=LastPass/api_delete_user.html&_LANG=enus
endpoint: `/enterpriseapi.php`,
retries: 10,
data: data
};

const res = await nango.post(config);

const isSuccess = res?.data?.status === 'OK';

return {
success: isSuccess
};
}
19 changes: 19 additions & 0 deletions integrations/lastpass/helpers/get-credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { NangoSync, NangoAction } from '../../models';

export async function getCredentials(nango: NangoSync | NangoAction): Promise<{ cid: number; provhash: string }> {
const connection = await nango.getConnection();

if ('username' in connection.credentials && 'password' in connection.credentials) {
const cid = connection.credentials['username'];
const provhash = connection.credentials['password'];

return {
cid,
provhash
};
} else {
throw new nango.ActionError({
message: `Credentials (username, password) are incomplete`
});
}
}
77 changes: 77 additions & 0 deletions integrations/lastpass/helpers/paginate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { NangoSync, ProxyConfiguration } from '../../models';

export interface LastPassPaginationParams {
endpoint: string;
cid: number;
provhash: string;
cmd: string;
pageSize?: number;
}

export interface LastPassPaginationResponse<T> {
results: T[];
}

export async function* paginate<T>(
nango: NangoSync,
{ endpoint, cid, provhash, cmd, pageSize = 100 }: LastPassPaginationParams
): AsyncGenerator<LastPassPaginationResponse<T>, void, undefined> {
let pageIndex = 0;

while (true) {
const body = {
cid,
provhash,
cmd,
data: {
pagesize: pageSize,
pageindex: pageIndex
}
};

const config: ProxyConfiguration = {
// eslint-disable-next-line @nangohq/custom-integrations-linting/include-docs-for-endpoints
endpoint,
retries: 10,
data: body
};
const response = await nango.post<{
total: number;
count: number;
Users: Record<string, any>;
invited: string[];
}>(config);

const users = Object.values(response.data.Users ?? {}).map((user) => ({
username: user.username,
fullname: user.fullname,
mpstrength: user.mpstrength,
created: user.created,
last_pw_change: user.last_pw_change,
last_login: user.last_login,
neverloggedin: user.neverloggedin,
disabled: user.disabled,
admin: user.admin,
totalscore: user.totalscore,
legacytotalscore: user.legacytotalscore,
hasSharingKeys: user.hasSharingKeys,
duousername: user.duousername,
sites: user.sites,
notes: user.notes,
formfills: user.formfills,
applications: user.applications,
attachments: user.attachments,
password_reset_required: user.password_reset_required
}));

if (users.length === 0 || users.length < pageSize) {
// eslint-disable-next-line @nangohq/custom-integrations-linting/no-object-casting
yield { results: users as T[] };
break;
}
// eslint-disable-next-line @nangohq/custom-integrations-linting/no-object-casting
yield { results: users as T[] };

pageIndex += 1;
}
}
11 changes: 11 additions & 0 deletions integrations/lastpass/mappers/to-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ReturnedUser } from '../types';
import type { User } from '../../models';

export function toUser(users: ReturnedUser[]): User[] {
return users.map((user) => ({
id: user.username,
firstName: user.fullname?.split(' ')[0] || '',
lastName: user.fullname?.split(' ')[1] || '',
email: user.username
}));
}
Loading

0 comments on commit 665f158

Please sign in to comment.