# 修改用户资料

该脚本会在管理员修改用户资料或者用户自己修改用户资料时执行。此脚本只在完全使用自定义数据库模式中需要。

# 函数定义

login 函数定义如下:

async function updateUser(id, updates, context) {
  // This script should update a user entry in your existing database. It will
  // be executed when a user attempts to change profile or when a user is updated
  // through the Auth0 dashboard or API.

  // The first argument `id` is the id of user.

  // The second argument `updates` contains following properties:
  // * email: the user's email
  // * username: the user's username
  // * phone: the user's phone number
  // * password: the user's password in plain text format
  // * nickname: the user's nickname
  // * photo: the user's photo
  // * and many other fields

  // The Second argument context contains information about the authentication context.
  // see http://core.authing.cn/connections/custom-db/config-custom-db-connection.html for more information.

  //
  // There are three ways this script can finish:
  // 1. A user was successfully created
  // format: https://docs.authing.co/user/profile.html .
  //    return null
  // 2. This user already exists in your database
  //    throw new Error("user allready exists")
  // 3. Something went wrong while trying to reach your database:
  //     throw new Error("my error message")

  const msg =
    'Please implement the Find User script for this database connection ';
  throw new Error(msg);
}
参数 类型 nullable 说明
id string / number false 用户 ID
updates object false 需要修改的用户字段
updates.email string ture 邮箱,该参数可能为空。
updates.phone string true 手机号,该参数可能为空。
updates.username string true 用户名,该参数可能为空。
updates.password string true 明文密码。该参数可能为空,强烈推荐使用 bcrypt 加密用户密码,详情见下文。
updates.nickname string true 用户昵称,该参数可能为空。
updates.photo string true 用户头像,该参数可能为空。
updates.token string true 用户 token,该参数可能为空。
updates.emailVerified bool true 邮箱是否验证,该参数可能为空。
updates.phoneVerified bool true 手机号是否验证,该参数可能为空。
updates.loginsCount number true 用户登录次数,该参数可能为空。
updates.xxxx any true 其他更多的用户字段,用户信息的详细格式请见:用户资料字段
context object true 请求上下文 context

其中 context 中包含包含以下信息:

属性名 类型 说明
userPoolId string 用户池 ID
userPoolName string 用户池 名称
userPoolMetadata object 用户池配置信息
appId string 当前用户的 ID,你可以通过 appId 区分用户请求的应用来源。
appName string 当前应用的 名称
appMetadata object 当前应用的配置信息
application string 用户池 ID
request object 当前请求的详细信息,包括:
ip: 客户端 IP
geo: 通过 IP 解析的客户端地理位置
body: 请求体

# 返回数据约定

# 修改成功

当修改用户资料成功时,你需要将该用户的最新用户信息返回给 Authing,用户信息的详细格式请见:用户资料字段 。示例:

async function updateUser(id, updates, context) {
  // Implement your logic here
  return {
    id: 1, // must not empty
    email: "test@example.com",
    emailVerified: true,
    nickname: "Nick",
    photo: ""
  }
}

# 用户不存在

当用户不存在时,你需要直接抛出错误(错误信息可自由定义),例如:

async function updateUser(id, updates, context) {
  // Implement your logic here
  throw new Error('User not exists!');
}

# 其他异常错误

当遇到其他异常错误时,你可以捕捉错误之后返回更友好的错误提示,例如:

async function updateUser(id, updates, context) {
  try {
    // Implement your logic here
  } catch (error) {
    throw new Error('Something went wrong ...')
  }
}

# 最佳实践

# 提供友好的错误提示

当遇到未知错误时,我们推荐使用抛出一个标准的 Error 对象,Authing 会捕捉此错误并最终返回给终端用户。例如:throw new Error("My nice error message"),你可以在自定义数据库的 日志历史 中看到该错误日志。

# 函数结束时断开数据库连接

请切记脚本执行完成时关闭到数据库的连接,比如调用 client.end(). 例如可以在 try/finallly 中执行确保其始终会被执行:

try {
  const result = await client.updates("YOUR updates");
} finally {
  // NOTE: always call `client.end()` here to close the connection to the database
  client.end();
}

# 示例函数

postgres 数据库为例,有以下几点说明:

  • 你可以通过 env.DB_CONNECTION_URI 获取数据库连接字符串用于创建数据库连接。
  • 根据 updates 中传过来的查询条件动态创建 update SQL 语句(updates.id, updates.email, updates.username, updates.phone 都可能为空,但不会同时为空)。
  • 如果 insertResult.rowCount 为 0,表明该用户不存在,抛出异常,错误信息为:User not exists! .
  • 最后返回指定格式的用户信息,用户信息的详细格式请见:用户资料字段
  • try/finally 中调用 client.end() 断开数据库连接。
async function updateUser(id, updates, context) {
  // This example uses the "pg" library
  // more info here: https://github.com/brianc/node-postgres
  const { Client } = require('pg');

  const client = new Client({
    connectionString: env.DB_CONNECTION_URI,
  });

  // Or you can:
  // const client = new Client({
  //   host: env.DB_HOST,
  //   port: env.DB_PORT,
  //   user: env.DB_USERNAME,
  //   password: env.DB_PASSWORD,
  //   database: env.DB_DATABASE,
  // });

  await client.connect();

  // Authing user attribute to database column
  const userColumnMap = {
    id: 'id',
    email: 'email',
    name: 'name',
    username: 'username',
    phone: 'phone',
    nickname: 'nickname',
    gender: 'gender',
    address: 'address',
    company: 'company',
    birthdate: 'birthdate',
    website: 'website',
    token: 'token',
    password: 'password',
    photo: 'photo',
    emailVerified: 'email_verified',
    phoneVerified: 'phone_verified',
    loginsCount: 'logins_count',
    lastIp: 'last_ip',
  };

  // Make sure to delete cols not exists in your table,
  // or sql might fail.
  for (const key in updates) {
    if (userColumnMap[key] === undefined) {
      delete updates[key];
    }
  }

  // If nothing interested, just return
  if (Object.keys(updates).length === 0) {
    return null;
  }

  function generateQuery(id, cols) {
    const _ = require('lodash');
    // Setup static beginning of query
    var query = ['UPDATE users'];
    query.push('SET');

    // Create another array storing each set command
    // and assigning a number value for parameterized query
    var set = [];
    Object.keys(cols)
      .filter(col => !!userColumnMap[col])
      .forEach(function(key, i) {
        set.push(userColumnMap[key] + ' = ($' + (i + 1) + ')');
      });
    query.push(set.join(', '));

    // Add the WHERE statement to look up by id
    query.push('WHERE id = ' + id);

    // Return all fields
    query.push('RETURNING *');

    // Return a complete query string
    return query.join(' ');
  }

  // Use bcrypt to encrypt password
  // more info here: https://github.com/kelektiv/node.bcrypt.js
  const bcrypt = require('bcrypt');

  try {
    const query = generateQuery(id, updates);
    const insertResult = await client.query(
      query,
      Object.keys(updates)
        .filter(col => !!userColumnMap[col])
        .map(key => {
          const val = updates[key];
          if (key === 'password') {
            // If key is password, use bcrypt to encrypt it
            return bcrypt.hashSync(val, bcrypt.genSaltSync(10));
          }
          return val;
        }),
    );
    if (insertResult.rowCount === 0) {
      throw new Error('User not exists!');
    }
    const user = insertResult.rows[0];
    return {
      id: user.id,
      email: user.email,
      name: user.name,
      phone: user.phone,
      username: user.username,
      photo: user.photo,
      nickname: user.nickname,
      token: user.token,
      emailVerified: user.email_verified,
      phoneVerified: user.phone_verified,
      loginsCount: user.logins_count,
      lastIp: user.last_ip,
      gender: user.gender,
      address: user.address,
      company: user.company,
      birthdate: user.birthdate,
      website: user.website,
    };
  } catch (error) {
    throw new Error(`Execute query failed: ${error.message}`);
  } finally {
    // NOTE: always call `client.end()` here to close the connection to the database
    client.end();
  }
}