Skip to main content

Get One User

getUser fetches a single user document by _id. Unlike getMe (which returns the caller), getUser can fetch any user the caller is allowed to see โ€” managers use it to inspect a team member, admins use it to view a profile before editing. It belongs to the user model and is the first act here that runs the full preAct chain including grantAccess.

The act lives in src/user/getUser/.

The validator (getUser.val.ts)โ€‹

set spreads activeRoleMixin (the activeRoleId string โ€” see Auth Utilities) and requires _id as an objectIdValidation. get is selectStruct("user", 2), so projections may include one level of relations.

import { object, objectIdValidation, string } from "lesan";
import { selectStruct } from "../../../mod.ts";
import { activeRoleMixin } from "@lib";

export const getUserValidator = () => {
return object({
set: object({
...activeRoleMixin,
_id: objectIdValidation,
}),
get: selectStruct("user", 2),
});
};
note

Runtime-agnostic imports "lesan" is this repo's path alias for the framework source. On npm/Bun you'd import from @hemedani/lesan, and on Deno from jsr:@hemedani/lesan. The @lib / ../../../mod.ts aliases stay as they are in your project (see Project Layout).

The registration (mod.ts)โ€‹

getUser runs the complete protection chain: setTokens (verify the JWT), setUser (load the fresh user), then grantAccess([{ roles: [...] }]). The role list here includes every non-ghost role (Manager, Admin, OrgHead, UnitHead, Employee, Ordinary), so any authenticated, role-bearing user may read any user. The ghost superuser bypasses the check entirely (grantAccess returns early when user.isGhost).

import { grantAccess, setTokens, setUser } from "@lib";
import { coreApp } from "../../../mod.ts";
import { getUserFn } from "./getUser.fn.ts";
import { getUserValidator } from "./getUser.val.ts";

export const getUserSetup = () =>
coreApp.acts.setAct({
schema: "user",
actName: "getUser",
preAct: [setTokens, setUser, grantAccess([{ roles: ["Manager", "Admin", "OrgHead", "UnitHead", "Employee", "Ordinary"] }])],
validator: getUserValidator(),
fn: getUserFn,
});

The implementation (getUser.fn.ts)โ€‹

Destructure _id from set and the whole get projection. Wrap _id in new ObjectId(...) and call user.findOne with the projection. If nothing was found, throw "user not found"; otherwise return the document.

import { type ActFn, ObjectId } from "lesan";
import { user } from "../../../mod.ts";
import { throwError } from "@lib";

export const getUserFn: ActFn = async (body) => {
const {
set: { _id },
get,
} = body.details;

const foundedUser = await user.findOne({
filters: { _id: new ObjectId(_id as string) },
projection: get,
});

!foundedUser && throwError("user not found");
return foundedUser;
};

This is the canonical Lesan read-one shape: findOne with filters + projection. Because password is in the model's excludes, selectStruct("user", 2) won't let a client request it โ€” the hash never leaves the database.

In the workflowโ€‹

getUser is the read-one counterpart of the admin list acts getUsers and countUsers: list to find _ids, then getUser for the detail view.

Run itโ€‹

Needs the token from login and an activeRoleId (any role from the user's roles array works, since getUser allows every role):

curl -X POST http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: <jwt>" \
-d '{
"model": "user",
"act": "getUser",
"details": {
"set": {
"activeRoleId": "<roleId>",
"_id": "<userId>"
},
"get": {
"_id": 1,
"first_name": 1,
"last_name": 1,
"email": 1,
"isActive": 1
}
}
}'

Grab <roleId> and <userId> from the roles[0].roleId and user._id in the login response.

Errors & fixesโ€‹

MessageSourceWhat it meansHow to fix
you should send your id with token key in req headersetTokensMissing token header.Add -H "token: <jwt>".
Invalid or expired tokensetTokensJWT didn't verify.Re-login.
Invalid or missing token datasetUserToken payload had no _id.Re-login.
user not existsetUserThe token's user was deleted.Use a different account or re-create it.
activeRoleId is requiredgrantAccessset.activeRoleId was omitted.getUser's validator requires activeRoleId โ€” always send it.
Active role not foundgrantAccessThe activeRoleId doesn't match any role on the authenticated user.Pick a roleId from the roles array in the login response.
You cant do thisgrantAccessThe active role isn't in the allowed list (or a scope check failed).For getUser this shouldn't happen for the listed roles โ€” it means the active role name is something unexpected.
user not foundgetUserFnNo user matches that _id.Check the _id (must be a 24-char hex ObjectId string); verify the user still exists.