From cafbc908daac4b661f2ee82cb584ab789b5b5ed3 Mon Sep 17 00:00:00 2001 From: Chen Junda Date: Tue, 25 Apr 2023 20:50:33 +0800 Subject: [PATCH] feat --- .changeset/cuddly-lobsters-pump.md | 7 +++++++ apps/auth/src/auth/AuthProvider.ts | 8 +++++++- apps/auth/src/auth/ldap/helpers.ts | 5 +++-- apps/auth/src/auth/ssh/index.ts | 21 ++++++++++++++++----- apps/auth/src/routes/getUser.ts | 8 ++++++-- apps/auth/tests/ldap/ldap.test.ts | 10 ++++++++-- apps/auth/tests/ssh.test.ts | 5 ++++- docs/docs/deploy/config/auth/ldap.md | 3 ++- docs/docs/deploy/config/auth/ssh.md | 3 ++- docs/docs/integration/auth/impl.md | 10 ++++++---- libs/auth/src/getUser.ts | 2 ++ 11 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 .changeset/cuddly-lobsters-pump.md diff --git a/.changeset/cuddly-lobsters-pump.md b/.changeset/cuddly-lobsters-pump.md new file mode 100644 index 0000000000..7ac6a73195 --- /dev/null +++ b/.changeset/cuddly-lobsters-pump.md @@ -0,0 +1,7 @@ +--- +"@scow/auth": minor +"@scow/lib-auth": minor +"@scow/docs": minor +--- + +认证系统 GET /user API 增加返回用户姓名和邮箱 diff --git a/apps/auth/src/auth/AuthProvider.ts b/apps/auth/src/auth/AuthProvider.ts index f83e45f0ce..3f1f0d617d 100644 --- a/apps/auth/src/auth/AuthProvider.ts +++ b/apps/auth/src/auth/AuthProvider.ts @@ -26,10 +26,16 @@ export type ValidateNameResult = "NotFound" | "Match" | "NotMatch"; export type CreateUserResult = "AlreadyExists" | "OK"; export type ChangePasswordResult = "NotFound" | "WrongOldPassword" | "OK"; +export interface UserInfo { + identityId: string; + name?: string; + mail?: string; +} + export interface AuthProvider { serveLoginHtml: (callbackUrl: string, req: FastifyRequest, rep: FastifyReply) => Promise; fetchAuthTokenInfo: (token: string, req: FastifyRequest) => Promise; - getUser: undefined | ((identityId: string, req: FastifyRequest) => Promise<{ identityId: string } | undefined>); + getUser: undefined | ((identityId: string, req: FastifyRequest) => Promise); createUser: undefined | ((info: CreateUserInfo, req: FastifyRequest) => Promise); validateName: undefined | ((identityId: string, name: string, req: FastifyRequest) => Promise); changePassword: undefined | ((id: string, oldPassword: string, newPassword: string, diff --git a/apps/auth/src/auth/ldap/helpers.ts b/apps/auth/src/auth/ldap/helpers.ts index 9b546180b7..a368d00f5a 100644 --- a/apps/auth/src/auth/ldap/helpers.ts +++ b/apps/auth/src/auth/ldap/helpers.ts @@ -128,9 +128,10 @@ export const extractUserInfoFromEntry = ( return undefined; } - const name = config.attrs.name ? takeOne(extractAttr(entry, config.attrs.name)) : identityId; + const name = config.attrs.name ? takeOne(extractAttr(entry, config.attrs.name)) : undefined; + const mail = config.attrs.mail ? takeOne(extractAttr(entry, config.attrs.mail)) : undefined; - return { identityId, name }; + return { identityId, name, mail }; }; export function takeOne(val: string | string[] | undefined) { diff --git a/apps/auth/src/auth/ssh/index.ts b/apps/auth/src/auth/ssh/index.ts index 936b2d1488..cda756e0fb 100644 --- a/apps/auth/src/auth/ssh/index.ts +++ b/apps/auth/src/auth/ssh/index.ts @@ -49,19 +49,30 @@ export const createSshAuthProvider = (f: FastifyInstance) => { registerPostHandler(f, loginNode); - return { + return { serveLoginHtml: (callbackUrl, req, rep) => serveLoginHtml(false, callbackUrl, req, rep), fetchAuthTokenInfo: async () => undefined, getUser: async (identityId, req) => { return await sshConnect(loginNode, "root", rootKeyPair, req.log, async (ssh) => { - return loggedExec(ssh, req.log, true, "id", [identityId]) - .then(() => ({ identityId })) - .catch(() => undefined); + + const resp = await loggedExec(ssh, req.log, false, "getent", ["passwd", identityId]); + + if (resp.code !== 0) { return undefined; } + + // https://en.wikipedia.org/wiki/Gecos_field + // ddadaal:x:1000:1000::/home/ddadaal:/bin/zsh + const gecosField = resp.stdout.split(":")[4]; + const fullName = gecosField.split(",")[0]; + + return { + identityId, + name: fullName, + }; }); }, validateName: undefined, createUser: undefined, changePassword: undefined, - }; + } satisfies AuthProvider; }; diff --git a/apps/auth/src/routes/getUser.ts b/apps/auth/src/routes/getUser.ts index 555f9037b2..efd6522829 100644 --- a/apps/auth/src/routes/getUser.ts +++ b/apps/auth/src/routes/getUser.ts @@ -18,7 +18,11 @@ const QuerystringSchema = Type.Object({ }); const ResponsesSchema = Type.Object({ - 200: Type.Object({ user: Type.Object({ identityId: Type.String() }) }), + 200: Type.Object({ user: Type.Object({ + identityId: Type.String(), + name: Type.Optional(Type.String()), + mail: Type.Optional(Type.String()), + }) }), 404: Type.Object({ code: Type.Literal("USER_NOT_FOUND") }), 501: Type.Null({ description: "此功能在当前服务器配置下不可用" }), }); @@ -48,7 +52,7 @@ export const getUserRoute = fp(async (f) => { const result = await f.auth.getUser(identityId, req); if (result) { - return rep.code(200).send({ user: { identityId: result.identityId } }); + return rep.code(200).send({ user: result }); } else { return rep.code(404).send({ code: "USER_NOT_FOUND" }); } diff --git a/apps/auth/tests/ldap/ldap.test.ts b/apps/auth/tests/ldap/ldap.test.ts index 4977ee678d..fb875a1aa1 100644 --- a/apps/auth/tests/ldap/ldap.test.ts +++ b/apps/auth/tests/ldap/ldap.test.ts @@ -37,6 +37,8 @@ const user = { captchaCode: "captchaCode", }; +const savedUserMail = "mail is " + user.mail; + class ConfigNoAddUserError extends Error {} if (!ldap.addUser) { @@ -117,6 +119,7 @@ it("creates user and group if groupStrategy is newGroupPerUser", async () => { expect(responseUser).toEqual({ dn: userDn, identityId: user.identityId, + mail: savedUserMail, name: user.name, }); @@ -201,7 +204,6 @@ it("should login with correct username and password", async () => { await createUser(); - // login const { payload, headers } = createFormData({ username: user.identityId, @@ -258,7 +260,11 @@ it("gets user info", async () => { }); expect(resp.statusCode).toBe(200); - expect(resp.json()).toEqual({ user: { identityId: user.identityId } }); + expect(resp.json()).toEqual({ user: { + identityId: user.identityId, + name: user.name, + mail: savedUserMail, + } }); }); it("returns 404 if user doesn't exist", async () => { diff --git a/apps/auth/tests/ssh.test.ts b/apps/auth/tests/ssh.test.ts index 13bba4f743..4c02b481c1 100644 --- a/apps/auth/tests/ssh.test.ts +++ b/apps/auth/tests/ssh.test.ts @@ -108,7 +108,10 @@ it("gets user info", async () => { }); expect(resp.statusCode).toBe(200); - expect(resp.json()).toEqual({ user: { identityId: username } }); + expect(resp.json()).toEqual({ user: { + identityId: username, + name: "Linux User", + } }); }); it("returns 404 if user doesn't exist", async () => { diff --git a/docs/docs/deploy/config/auth/ldap.md b/docs/docs/deploy/config/auth/ldap.md index 912fba9bc0..6ba5741605 100644 --- a/docs/docs/deploy/config/auth/ldap.md +++ b/docs/docs/deploy/config/auth/ldap.md @@ -12,6 +12,7 @@ LDAP认证系统支持的功能如下表: | 功能 | 是否支持 | | ---------------- | ------------------------ | | 用户登录 | 是 | +| 获取用户信息 | 是 | | 用户创建 | 如果配置了相关配置即支持 | | 用户名和姓名验证 | 是 | | 修改密码 | 是 | @@ -120,7 +121,7 @@ ldap: # 3. 管理系统添加用户时,验证ID和姓名是否匹配 # # 如果不设置此字段,那么 - # 1. 用户姓名为用户的ID + # 1. 用户显示的姓名为用户的ID # 2. 创建用户时姓名信息填入LDAP # 3. 管理系统添加用户时,不验证ID与姓名是否匹配 # name: cn diff --git a/docs/docs/deploy/config/auth/ssh.md b/docs/docs/deploy/config/auth/ssh.md index 1d2f1ba569..023e4d6474 100644 --- a/docs/docs/deploy/config/auth/ssh.md +++ b/docs/docs/deploy/config/auth/ssh.md @@ -9,13 +9,14 @@ title: SSH SSH认证是非常简单的认证方式。用户可以直接使用和SSH登录集群相同的用户名和密码来登录系统。 -在此认证方式中,用户的用户ID为其对应的Linux用户名。 +在此认证方式中,用户的用户ID为其对应的Linux用户名,用户的姓名为其对应的Linux用户的[Gecos Field](https://en.wikipedia.org/wiki/Gecos_field)的full name字段。 SSH认证方式所支持的功能如下表: | 功能 | 是否支持 | | ---------------- | -------- | | 用户登录 | 是 | +| 获取用户信息 | 是 | | 用户创建 | 否 | | 用户名和姓名验证 | 否 | | 修改密码 | 否 | diff --git a/docs/docs/integration/auth/impl.md b/docs/docs/integration/auth/impl.md index c5eddc6f7f..b692275f18 100644 --- a/docs/docs/integration/auth/impl.md +++ b/docs/docs/integration/auth/impl.md @@ -155,9 +155,11 @@ SCOW中使用`identityId`标识一个用户,并同时使用此`identityId`作 ##### 200 OK -| 字段 | 类型 | 是否必须 | 解释 | -| ----------------- | ------ | -------- | -------------------------------- | -| `user.identityId` | 字符串 | 是 | 用户的ID。和请求的identityId一致 | +| 字段 | 类型 | 是否必须 | 解释 | +| ----------------- | ------ | -------- | -------------------------------------------------------------------------- | +| `user.identityId` | 字符串 | 是 | 用户的ID。和请求的identityId一致 | +| `user.name` | 字符串 | 否 | 用户的姓名。如果认证系统可以获取用户的姓名,则返回。如果不能获取,就不设置 | +| `user.mail` | 字符串 | 否 | 用户的邮箱。如果认证系统可以获取用户的邮箱,则返回。如果不能获取,就不设置 | ##### 404 Not Found @@ -167,7 +169,7 @@ SCOW中使用`identityId`标识一个用户,并同时使用此`identityId`作 #### 解释 -此API可以获取用户的信息,当前返回的用户信息只包含用户的ID。此API也可以用于获取用户是否存在。 +此API可以获取用户的信息。此API也可以用于获取用户是否存在。 ## 修改密码相关API diff --git a/libs/auth/src/getUser.ts b/libs/auth/src/getUser.ts index 9a4244c298..6eaa4599b2 100644 --- a/libs/auth/src/getUser.ts +++ b/libs/auth/src/getUser.ts @@ -16,6 +16,8 @@ import { Logger } from "ts-log"; export interface AuthUserInfo { identityId: string; + name?: string; + mail?: string; } /**