Skip to content

Commit

Permalink
Merge pull request #2 from descope-sample-apps/generalized-password-m…
Browse files Browse the repository at this point in the history
…igration

Added Password Migration Guides for Cleartext
  • Loading branch information
gaokevin1 authored Nov 6, 2024
2 parents a1ad752 + 635bb7e commit 47b2371
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 142 deletions.
44 changes: 38 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,54 @@
# Django to Descope User Migration Tool
# Custom Data Store to Descope Migration Tool

> If you're interested in our Third Party Service Migration Tool (for Auth0, Cognito, and Firebase), you can visit the repository [here](https://github.com/descope/descope-migration).
> If youre looking to migrate from third-party services like Auth0, Cognito, or Firebase, check out our dedicated [Third Party Service Migration Tool repository](https://github.com/descope/descope-migration).
Easily migrate your users from a Django-based custom data store to Descope with our robust migration script. We provide support for both Node.js and Python, complete with language-specific setup instructions, making the transition seamless and efficient.
This tool provides an efficient way to migrate your users from any custom data store to Descope, whether you're using plain-text passwords or hashed passwords with various algorithms. We support both Node.js and Python implementations, with detailed instructions for each language, to ensure a seamless migration experience.

## 🚀 Overview

Migrating your users to Descope is straightforward with our dedicated tools. Whether you prefer working in Node.js or Python, we've got you covered. These scripts are designed to integrate smoothly with your existing Django user model, ensuring that all user data is accurately and securely transferred to Descope.
Migrating users to Descope from a custom data store is simplified with our comprehensive migration script. This tool is designed to work with various password storage formats, allowing you to securely and accurately transfer user data to Descope.

### Supported Password Types

Our tool supports migration of both plain-text and hashed passwords. If your custom data store uses hashed passwords, we support algorithms such as `bcrypt`, `PBKDF2`, `Django`, and more. This flexibility allows you to retain your current password configurations and avoid requiring users to reset their passwords.

### Multi-Language Support

You can run the migration in either Node.js or Python, depending on your environment and preferences:

- **Node.js**: Offers a JavaScript-based approach with full compatibility for Descope’s API.
- **Python**: Provides a Pythonic migration experience that integrates seamlessly with Descope’s User Management API.

## 🛠️ Setup

### Node.js

Follow the [Node.js README](./node) for setup instructions and usage details.
1. Clone the repository and navigate to the Node.js folder.
2. Follow the [Node.js README](./node) for detailed setup instructions, including environment configuration and usage.

### Python

Refer to the [Python README](./python) for setup instructions and usage details.
1. Clone the repository and navigate to the Python folder.
2. Refer to the [Python README](./python) for detailed setup instructions, including environment configuration and usage.

Both versions are designed to handle different hashing algorithms and will prompt you for any specific configurations needed for your data store.

## 🔑 Password Migration

> Learn more about Descope's password hashing algorithm support on our [docs page](https://docs.descope.com/migrate/custom#importing-passwords)
For password migration, our tool supports a wide range of hashing algorithms. If using plain-text passwords, the tool will automatically hash passwords in Descope-compatible formats. If using hashed passwords, ensure that you configure the tool with your hashing algorithm (e.g., `UserPasswordDjango`, `bcrypt`) to maintain compatibility with Descope’s User Management API.

Supported algorithms include:

1. **bcrypt**
2. **PBKDF2**
3. **Django**
4. **Firebase**
5. **PHPass**
6. **MD5** (for legacy compatibility)

With this flexibility, your users will be able to sign in with their existing passwords seamlessly after migration.

## 📜 License

Expand Down
127 changes: 117 additions & 10 deletions node/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,127 @@
# Node
# Node.js User Migration (Custom Data Store)

This Node.js script enables you to migrate users from a custom data store to Descope, supporting both plain-text and hashed passwords. It is customizable to handle user roles, email verification status, and other user attributes.

## 📄 CSV Input Format

The migration script expects a CSV file with the following fields:

| Field | Description |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `identifier` | Unique identifier for the user (e.g., email or username). |
| `email` | User's email address, used for login and communication. |
| `verifiedEmail` | Boolean field indicating if the email is verified. |
| `displayName` | Display name of the user, typically used in the UI. |
| `roleNames` | Array of roles associated with the user (e.g., `admin`, `user`). |
| `password` | User's password hash. Supported formats include plain text or specific hashing algorithms like PBKDF2. |

### Example CSV Input

```csv
identifier,email,verifiedEmail,displayName,roleNames,password
[email protected],[email protected],TRUE,Test One,[],pbkdf2_sha256$260000$qvuPAH7BzZx5oB278YWjNO$ydHrFpnbLr+udgJkquUusfG3YQBI6+ZvxsnVbdLserw=
[email protected],[email protected],FALSE,Test Two,[],
[email protected],[email protected],TRUE,Test Three,"[""admin"", ""super_user""]",pbkdf2_sha256$260000$D60FxGGluDfVTANezkv6lO$8qUrthZYWiCn3YD3mc3ML9nVQd3FRi+jkpAQ0zmUGCY=
```

- **Password Field**: Supports hashed passwords in formats like PBKDF2. The `password` field should be either blank or contain the hashed password string.
- **RoleNames Field**: Specify roles as a JSON array, e.g., `["admin", "super_user"]`.

## ⚙️ Setup

1. Add environment variables
### 1. Add Environment Variables

Set up the necessary Descope environment variables. Replace `<your descope project id>` and `<your descope management key>` with your actual values:

```sh
export DESCOPE_PROJECT_ID=<your descope project id>
export DESCOPE_MANAGEMENT_KEY=<your descope management key>
```
DESCOPE_PROJECT_ID=<your descope project id>
DESCOPE_MANAGEMENT_KEY=<your descope management key>

- **Project ID**: Can be found [here](https://app.descope.com/settings/project).
- **Management Key**: Can be found [here](https://app.descope.com/settings/company/managementkeys).

### 2. Install Dependencies and Run the Script

Install dependencies and start the migration script:

```sh
npm install
npm start
```
The Project ID can be found [here](https://app.descope.com/settings/project).
The Descope Management Key can be found [here](https://app.descope.com/settings/company/managementkeys).

The script will process your CSV data, format it to Descope’s requirements, and use the Descope User Management API to create user records, assign roles, and migrate passwords.

2. Install dependencies:
## 🔑 Password Migration

To migrate passwords, the script can handle both plain-text and hashed passwords. By default, it expects a plain-text password, but you can modify it to use hashed passwords, like this:

```javascript
if (user.password) {
// Use this block if you’re importing hashed passwords
body.hashedPassword = {
django: {
hash: user.password,
},
};
// Uncomment for plain-text passwords
// body.password = user.password;
}
```
npm i
npm start
```

Replace `django` with the correct algorithm if you’re using a different format (e.g., `bcrypt`, `pbkdf2`). For more information on supported hashing algorithms, refer to [Descope’s documentation](https://docs.descope.com/migrate/custom#importing-passwords).

## 📄 Using the `APIUser` Type and Adding Custom Attributes

The `APIUser` type defines the structure of a user object. You can use it to include additional attributes like `phone` and `customAttributes`.

Here’s how you can construct the `body` object in the script using `APIUser`:

```javascript
let body: APIUser = {
loginId: user.email,
email: user.email,
displayName: user.displayName,
verifiedEmail: user.verifiedEmail,
};

// Add optional attributes
if (user.phone) {
body.phone = user.phone; // Add phone if available
}

if (user.customAttributes) {
body.customAttributes = {
preferredLanguage: user.customAttributes.language,
timezone: user.customAttributes.timezone,
};
}

if (user.password) {
// Use hashed passwords if necessary
body.hashedPassword = {
django: {
hash: user.password,
},
};
// Uncomment for plain-text passwords
// body.password = user.password;
}
```

### Explanation of Additional Attributes

- **Phone**: Add `phone` if present in the CSV data. It must be a string.
- **Custom Attributes**: Use `customAttributes` to include any additional key-value pairs, such as `preferredLanguage` or `timezone`. Adjust the structure based on the fields in your CSV.

### Example Updated CSV with Additional Attributes

To migrate with these custom attributes, update the CSV to include columns for `phone` and `customAttributes`, if needed:

```csv
identifier,email,verifiedEmail,displayName,roleNames,password,phone,customAttributes
[email protected],[email protected],TRUE,Test One,[],pbkdf2_sha256$260000$qvuPAH7BzZx5oB278YWjNO$ydHrFpnbLr+udgJkquUusfG3YQBI6+ZvxsnVbdLserw=,1234567890,"{""language"": ""en"", ""timezone"": ""UTC""}"
[email protected],[email protected],FALSE,Test Two,[],,0987654321,"{""language"": ""es"", ""timezone"": ""PST""}"
```

With these updates, you can easily customize and extend the user migration process to handle various attributes beyond the basic setup, ensuring that all relevant data is transferred accurately to Descope.
112 changes: 57 additions & 55 deletions node/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,84 @@
import { APIUser, UserResponse, User, Status } from "./types";

const getBearer = () => {

if (process.env.DESCOPE_PROJECT_ID === undefined) {
throw new Error('DESCOPE_PROJECT_ID is not defined');
throw new Error("DESCOPE_PROJECT_ID is not defined");
}

if (process.env.DESCOPE_MANAGEMENT_KEY === undefined) {
throw new Error('DESCOPE_MANAGEMENT_KEY is not defined');
throw new Error("DESCOPE_MANAGEMENT_KEY is not defined");
}

const bearer = `Bearer ${process.env.DESCOPE_PROJECT_ID}:${process.env.DESCOPE_MANAGEMENT_KEY}`;
return bearer;
}
};
export const createUser = async (user: User) => {
let body: APIUser = {
loginId: user.email,
email: user.email,
displayName: user.displayName,
verifiedEmail: user.verifiedEmail,
}
if (user.password) {
body.hashedPassword = {
django: {
hash: user.password
}
}
}

try {
const res = await fetch('https://api.descope.com/v1/mgmt/user/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': getBearer()
},
body: JSON.stringify(body)
});

const data = await res.json();
if (data.errorCode) {
console.error(data);
return;
}
const createdUser: UserResponse = data.user;
return createdUser;
let body: APIUser = {
loginId: user.email,
email: user.email,
displayName: user.displayName,
verifiedEmail: user.verifiedEmail,
};

if (user.password) {
// Use this block if you’re importing hashed passwords
body.hashedPassword = {
django: {
hash: user.password,
},
};
// Uncomment this line for plain-text passwords
// body.password = user.password;
}

try {
const res = await fetch("https://api.descope.com/v1/mgmt/user/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: getBearer(),
},
body: JSON.stringify(body),
});

const data = await res.json();
if (data.errorCode) {
console.error(data);
return;
}
catch (e) {
const createdUser: UserResponse = data.user;
return createdUser;
} catch (e) {
console.error("Error creating user: ", e);
}

}
};

export const updateUserStatus = async (loginId: string, status: Status) => {
const body = {
loginId,
status
}
status,
};
try {
const res = await fetch('https://api.descope.com/v1/mgmt/user/update/status', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': getBearer()
},
body: JSON.stringify(body)
});

const res = await fetch(
"https://api.descope.com/v1/mgmt/user/update/status",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: getBearer(),
},
body: JSON.stringify(body),
}
);

const data = await res.json();
if (data.errorCode) {
console.error(data);
return;
}
const updateUser: UserResponse = data.user;
return updateUser;
}
catch (e) {
} catch (e) {
console.error("Error updating user status: ", e);
}
}
};
Loading

0 comments on commit 47b2371

Please sign in to comment.