From 68273802ccc73ed81cdbd513a37f48779a2075aa Mon Sep 17 00:00:00 2001 From: Douglas Barone Date: Fri, 15 Dec 2023 15:55:53 -0400 Subject: [PATCH] Basic server setup --- .gitignore | 3 +- .prettierrc | 14 ++++++ README.md | 33 ++------------ package-lock.json | 75 +++++++++++++++++++++++++++++--- package.json | 2 + src/server/index.ts | 2 + src/server/lib/encodePassword.ts | 12 +++++ src/server/lib/updatePassword.ts | 66 ++++++++++++++++++++++++++++ src/server/trpc.ts | 16 +++++-- 9 files changed, 182 insertions(+), 41 deletions(-) create mode 100755 .prettierrc create mode 100644 src/server/lib/encodePassword.ts create mode 100644 src/server/lib/updatePassword.ts diff --git a/.gitignore b/.gitignore index 403adbc..834fd88 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ node_modules /dist - # local env files .env.local .env.*.local @@ -21,3 +20,5 @@ pnpm-debug.log* *.njsproj *.sln *.sw? + +.env diff --git a/.prettierrc b/.prettierrc new file mode 100755 index 0000000..d9bd310 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "bracketSpacing": true, + "arrowParens": "avoid", + "overrides": [ + { + "files": "*.js, *.vue, *.css, *.scss", + "excludeFiles": "**/dist/**, **/node_modules/**" + } + ] +} diff --git a/README.md b/README.md index 50b30e0..bbf2f43 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,17 @@ -# default +# IFMS Password updater - Web ## Project setup -``` -# yarn -yarn - -# npm npm install -# pnpm -pnpm install -``` - ### Compiles and hot-reloads for development -``` -# yarn -yarn dev - # npm -npm run dev -# pnpm -pnpm dev -``` +npm run dev ### Compiles and minifies for production -``` -# yarn -yarn build - # npm + npm run build - -# pnpm -pnpm build -``` - -### Customize configuration - -See [Configuration Reference](https://vitejs.dev/config/). diff --git a/package-lock.json b/package-lock.json index a4f7801..ccb5d8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,9 @@ "@trpc/server": "^10.44.1", "body-parser": "^1.20.2", "cors": "^2.8.5", + "dotenv": "^16.3.1", "express": "^4.18.2", + "ldapts": "^7.0.7", "roboto-fontface": "*", "vue": "^3.2.0", "vue-router": "^4.0.0", @@ -589,6 +591,14 @@ "node": ">=18.0.0" } }, + "node_modules/@types/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-V91DSJ2l0h0gRhVP4oBfBzRBN9lAbPUkGDMCnwedqPKX2d84aAMc9CulOvxdw1f7DfEYx99afab+Rsm3e52jhA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -663,7 +673,6 @@ "version": "18.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", - "devOptional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -707,6 +716,11 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==" + }, "node_modules/@types/webfontloader": { "version": "1.6.38", "resolved": "https://registry.npmjs.org/@types/webfontloader/-/webfontloader-1.6.38.tgz", @@ -1235,6 +1249,14 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1544,7 +1566,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -1617,6 +1638,17 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2568,6 +2600,22 @@ "json-buffer": "3.0.1" } }, + "node_modules/ldapts": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-7.0.7.tgz", + "integrity": "sha512-c/W9jZJ2WiGiAIBnkzUmYkIw1d5jebfVfp+vpR9Z6xh4QXm+kwwaQbmHFJHKsW4OwFARdOaQ03H3fBky4+XcVg==", + "dependencies": { + "@types/asn1": ">=0.2.4", + "@types/uuid": ">=9", + "asn1": "~0.2.6", + "debug": "~4.3.4", + "strict-event-emitter-types": "~2.0.0", + "uuid": "~9.0.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2743,8 +2791,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/muggle-string": { "version": "0.3.1", @@ -3471,6 +3518,11 @@ "node": ">= 0.8" } }, + "node_modules/strict-event-emitter-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz", + "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3657,8 +3709,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -3701,6 +3752,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 2c3cd46..005515d 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "@trpc/server": "^10.44.1", "body-parser": "^1.20.2", "cors": "^2.8.5", + "dotenv": "^16.3.1", "express": "^4.18.2", + "ldapts": "^7.0.7", "roboto-fontface": "*", "vue": "^3.2.0", "vue-router": "^4.0.0", diff --git a/src/server/index.ts b/src/server/index.ts index a8e3c8c..0f27d9f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,3 +1,5 @@ +import "dotenv/config"; + import { server } from "./server"; process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; diff --git a/src/server/lib/encodePassword.ts b/src/server/lib/encodePassword.ts new file mode 100644 index 0000000..4bde902 --- /dev/null +++ b/src/server/lib/encodePassword.ts @@ -0,0 +1,12 @@ +export function encodePassword(password: string): string { + let encodedPassword = ""; + password = '"' + password + '"'; + + for (let i = 0; i < password.length; i++) + encodedPassword += String.fromCharCode( + password.charCodeAt(i) & 0xff, + (password.charCodeAt(i) >>> 8) & 0xff + ); + + return encodedPassword; +} diff --git a/src/server/lib/updatePassword.ts b/src/server/lib/updatePassword.ts new file mode 100644 index 0000000..bd02bdd --- /dev/null +++ b/src/server/lib/updatePassword.ts @@ -0,0 +1,66 @@ +import { Client, Change, Attribute } from 'ldapts' +import { encodePassword } from './encodePassword' + +const ldapClient = new Client({ + url: process.env.LDAP_URL || 'ldap://10.1.0.16' +}) + +const bindUser = process.env.AD_BIND_USER || '' +const bindPassword = process.env.AD_BIND_PASSWORD || '' +const baseDN = process.env.AD_BASE_DN || '' + +async function getUserDN(username: string): Promise { + try { + await ldapClient.bind(bindUser, bindPassword) + + const { searchEntries } = await ldapClient.search(baseDN, { + scope: 'sub', + attributes: ['dn'], + filter: `(sAMAccountName=${username})` + }) + + return searchEntries[0]?.dn + } catch (err) { + console.error(err) + } finally { + await ldapClient.unbind() + } + + throw new Error('User not found') +} + +export async function updatePassword({ + username, + password, + newPassword +}: { + username: string + password: string + newPassword: string +}) { + try { + const userDN = await getUserDN(username) + await ldapClient.bind(userDN, password) + + await ldapClient.modify(userDN, [ + new Change({ + operation: 'delete', + modification: new Attribute({ + type: 'unicodePwd', + values: [encodePassword(password)] + }) + }), + new Change({ + operation: 'add', + modification: new Attribute({ + type: 'unicodePwd', + values: [encodePassword(newPassword)] + }) + }) + ]) + } catch (err) { + console.error(err) + } finally { + await ldapClient.unbind() + } +} diff --git a/src/server/trpc.ts b/src/server/trpc.ts index 2021d6b..7e58bc6 100644 --- a/src/server/trpc.ts +++ b/src/server/trpc.ts @@ -1,16 +1,24 @@ import { initTRPC, TRPCError } from "@trpc/server"; import * as trpcExpress from "@trpc/server/adapters/express"; -// import { z } from "zod"; +import { z } from "zod"; -export const t = initTRPC.create(); +export const { procedure, router } = initTRPC.create(); -const { query, mutation, input } = t.procedure; +const { query, mutation, input } = procedure; -export const appRouter = t.router({ +export const appRouter = router({ hello: query(async () => { return "Hello World!"; }), + + updatePassword: input( + z.object({ + username: z.string(), + password: z.string(), + newPassword: z.string().min(8), + }) + ).mutation(async () => {}), }); // export type definition of API