Skip to content

Commit

Permalink
Deactivate user command (#14)
Browse files Browse the repository at this point in the history
* initial commit

* redo error and response catching

* boilerplate command

* write initial attempt at command

* dumbass added ! in wrong spot

* supplied wrong user to the admin api req

* forgot to add request type

* just being dumb

* login part of deactivation

* forgot async await

* test no stringify

* dumbass forgetting to await shit

* a

* test if got access token

* install node-fetch

* mfw .string().json()

* remove node fetch

* cant read response body twice because fuck me i guess

* asdafg

* more debug

* see if delay fixes it

* add change pfp and displayname

* called wrong var

* tried to set avatar to displayname

* add user deactivation
  • Loading branch information
jjj333-p authored Jan 16, 2024
1 parent d0530f1 commit 76e5780
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 19 deletions.
11 changes: 10 additions & 1 deletion examples/login.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@ prefix: '.'
authorized-users:
- '@bob:example.org'

# Deactivated user PFP
# This is the profile image (in mxc:// format) that will be set to user profiles before deactivation. if left blank it will simply be removed
# Recommended is blank or `mxc://matrix.org/LaVsSRIBaLkOwvehbwDxEDio`
deactivatedpfp: ''

# Deactivated user DisplayName
# Display name to set to any deactivated user. Unsure what will happen if left blank.
# Recommended is `Deactivated User`
deactivateddn: 'Deactivated User'

# Location of dendrite.yaml
# the interface steals database information from the dendrite.yaml configuration file to work
# make sure whatever user is running the interface has permissions to open the dendrite.yaml file
dendriteyaml: '/opt/dendrite/dendrite.yaml'
dendriteyaml: '/opt/dendrite/dendrite.yaml'
143 changes: 142 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

//Import dependencies
import { AutojoinRoomsMixin, MatrixClient, SimpleFsStorageProvider } from "matrix-bot-sdk";
import fs from "fs";
Expand All @@ -14,6 +18,8 @@ let adminRoom = loginParsed["administration-room"];
const prefix = loginParsed["prefix"]
const authorizedUsers = loginParsed["authorized-users"];
const dendriteyaml = loginParsed["dendriteyaml"]
const deactivatedpfp = loginParsed["deactivatedpfp"]
const deactivateddn = loginParsed["deactivateddn"]

//if the interface config does not supply a path
if (!dendriteyaml){
Expand Down Expand Up @@ -45,7 +51,6 @@ if(!dendriteconfig["global"]){
process.exit(1)
}


//the bot sync something idk bro it was here in the example so i dont touch it ;-;
const storage = new SimpleFsStorageProvider("bot.json");

Expand Down Expand Up @@ -181,6 +186,65 @@ async function makeDendriteReq (reqType, command, arg1, arg2, body) {

}

async function makeUserReq (reqType, command, arg1, arg2, userToken, body, ) {

//base url guaranteed to always be there
//Dendrite only accepts requests from localhost
let url = "http://localhost:" + port + "/_matrix/client/v3/" + command

//if there is a first argument add it
if (arg1) url += ("/" + arg1)

//if there is a second argument add it
if (arg2) url += ("/" + arg2)

//if body is supplied, stringify it to send in http request
let bodyStr = null
if (body) bodyStr = JSON.stringify(body)

try {

//make the request and return whatever the promise resolves to
var response = await (await fetch(url, {
method: reqType,
headers: {
"Authorization": "Bearer " + userToken,
"Content-Type": "application/json"
},
body:bodyStr
})).json()

//.catch
} catch (e) {
client.sendHtmlNotice(adminRoom, ("❌ | could not make <code>"+ url + "</code> request with error\n<pre><code>" + e + "</code></pre>"))
}

//.then
client.sendHtmlNotice(adminRoom, ("Ran <code>"+ url + "</code> with response <pre><code>" + JSON.stringify(response) + "</code></pre>"))

return response

}

async function resetUserPwd (localpart, password, logout){

let userMxid = "@" + localpart + ":" + server

if (!password) password = generateSecureOneTimeCode(35)

makeDendriteReq("POST", "resetPassword", userMxid, null, {
password:password,
logout_devices:logout
})

return (password)

}

async function evacuateUser(mxid){
makeDendriteReq("POST", "evacuateUser", mxid)
}

async function resetUserPwd (localpart, password, logout){

let userMxid = "@" + localpart + ":" + server
Expand Down Expand Up @@ -336,6 +400,83 @@ commandHandlers.set("passwd", async ({contentByWords, event}) => {

})

commandHandlers.set("deactivate", async ({contentByWords, event}) => {

//first argument provided
let user = contentByWords[1]
if(!user) {

client.sendHtmlNotice(adminRoom, ("❌ | no user indicated."))

return;
}

//remove the @ no matter if its a mxid or localpart
//user may mistakenly provide @localpart or localpart:server.tld and that is okay
// .substring(1) just removes the first char
if(user.startsWith('@')) user = user.substring(1)

//decides if its a mxid or localpart
if(user.includes(":")){

//if its not a local user we cant do anything
if(!user.endsWith(":" + server)){

client.sendHtmlNotice(adminRoom, ("❌ | <code>" + contentByWords[1] + "</code> does not appear to be a valid user ID."))

return;
}

//we want only the localpart
//while there are normal restrictions on user account chars, @ and : are the only characters that truly cannot be allowed
//it is possible for admins to modify dendrite to remove those restrictions, and this interface need not restrict to that needlessly
user = user.split(":")[0]

}

//reset the password as to lock out the user
let newpwd = await resetUserPwd(user, null, true)

//idk some race conditions, this makes it work more reliably so sure
await delay(1000)

//make login request
let response = await makeUserReq("POST", "login", null, null, null, {
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": user,
},
"password": newpwd,
})

let userToken = response["access_token"]

//no token means no successful login
if (!userToken) {

client.sendNotice(adminRoom, "❌ | unable to log in. This may just be a momentary error.")

return;
}

let userMxid = "@" + user + ":" + server

//sanatize pfp and displayname
await makeUserReq("PUT", "profile", userMxid, "avatar_url", userToken, {"avatar_url":deactivatedpfp})
await makeUserReq("PUT", "profile", userMxid, "displayname", userToken, {"displayname":deactivateddn})

//deactivate the account
await makeUserReq("POST", "account", "deactivate", null, userToken, {
"auth": {
"type": "m.login.password",
"user": user,
"password": newpwd,
},
})

})

//data structure to hold various handlers
let eventHandlers = new Map()

Expand Down
35 changes: 18 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 76e5780

Please sign in to comment.