Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5665504bc1 | |||
| 2ac1c30112 | |||
| 04c18eaf30 | |||
| 6835074b8b |
@@ -4,6 +4,12 @@ All notable changes to this project are documented here. Format loosely
|
|||||||
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
||||||
correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
|
correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
|
||||||
|
|
||||||
|
## [1.5.1] - 2026-07-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **`PUT /api/user/:uid` 500'd with `ObjectClassViolationError` (LDAP `0x41`) when setting `sshPublicKey`** on any account created before the `ldapPublicKey` auxiliary objectClass was added to new-user creation (e.g. the bootstrap `admin` account). `User.update`'s `sshPublicKey` handling and `User.addSSHkey` (`nodejs/models/user_ldap.js`) now add the `ldapPublicKey` objectClass first (ignoring `TypeOrValueExistsError` if already present), the same pattern already used for `dateOfBirth`/`theta42Person`.
|
||||||
|
- **OAuth Integration parent dropdown was blank.** `populateHostDropdown` in `nodejs/views/directory.ejs` only built options for `kind === 'host'` and `kind === 'service'` — there was no branch for `kind === 'oauth'`, so choosing "OAuth Integration" in the Directory's add-resource modal left the parent-Service picker empty except the placeholder. Added the missing branch.
|
||||||
|
|
||||||
## [1.5.0] - 2026-07-26
|
## [1.5.0] - 2026-07-26
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ on, just like anyone else's.
|
|||||||
|
|
||||||
A **group** is just a named list of accounts, used to control access. This
|
A **group** is just a named list of accounts, used to control access. This
|
||||||
app has a handful of built-in groups that grant admin powers (e.g. only
|
app has a handful of built-in groups that grant admin powers (e.g. only
|
||||||
people in the `app_sso_admin` group can see the Users/Groups/Integrations
|
people in the `app_sso_admin` group can see the Users/Groups/Directory/Executive
|
||||||
pages at all), but you can also make your own groups for any app you
|
pages at all), but you can also make your own groups for any app you
|
||||||
connect — say, a group listing everyone who should be allowed into your
|
connect — say, a group listing everyone who should be allowed into your
|
||||||
photo server. Once a group exists, add or remove members from the
|
photo server. Once a group exists, add or remove members from the
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ what matters practically is the handful of concepts below.
|
|||||||
## What's a "client"?
|
## What's a "client"?
|
||||||
|
|
||||||
Every app you connect is registered here as a **client** — a single entry
|
Every app you connect is registered here as a **client** — a single entry
|
||||||
on the Integrations page representing that one app. Registering a client
|
in the Directory representing that one app. Registering a client
|
||||||
gives you a **Client ID** and **Client Secret**: think of these like a
|
gives you a **Client ID** and **Client Secret**: think of these like a
|
||||||
username and password, but for the *app itself* rather than for a person.
|
username and password, but for the *app itself* rather than for a person.
|
||||||
You paste them into the other app's own "Single Sign-On" or "OIDC" setup
|
You paste them into the other app's own "Single Sign-On" or "OIDC" setup
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ Resources carry a flexible `metadata` JSON object that can store essential conte
|
|||||||
|
|
||||||
The Directory Management interface provides a **Tree View** toggle that visually nests your resources, making it easy to comprehend your network topography at a glance. You can also filter, search, and sort your entire infrastructure inventory. From the tree view, you can click the green `+` icon next to any resource to instantly add a child resource beneath it.
|
The Directory Management interface provides a **Tree View** toggle that visually nests your resources, making it easy to comprehend your network topography at a glance. You can also filter, search, and sort your entire infrastructure inventory. From the tree view, you can click the green `+` icon next to any resource to instantly add a child resource beneath it.
|
||||||
|
|
||||||
|
<a href="images/directory.png" target="_blank"><img src="images/directory.png" alt="Directory & inventory list view" width="80%"></a>
|
||||||
|
|
||||||
## Slug conventions
|
## Slug conventions
|
||||||
|
|
||||||
Slugs are the stable identifiers automation keys off, so the tooling around the SSO Manager follows a shared convention:
|
Slugs are the stable identifiers automation keys off, so the tooling around the SSO Manager follows a shared convention:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 430 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 313 KiB |
|
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 221 KiB |
@@ -22,10 +22,11 @@ one command).
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<a href="images/dashboard.png" target="_blank"><img src="images/dashboard.png" alt="Dashboard" width="49%"></a>
|
<a href="images/dashboard.png" target="_blank"><img src="images/dashboard.png" alt="Executive dashboard" width="49%"></a>
|
||||||
<a href="images/users.png" target="_blank"><img src="images/users.png" alt="User list" width="49%"></a>
|
<a href="images/users.png" target="_blank"><img src="images/users.png" alt="User list" width="49%"></a>
|
||||||
<a href="images/groups.png" target="_blank"><img src="images/groups.png" alt="Groups" width="49%"></a>
|
<a href="images/groups.png" target="_blank"><img src="images/groups.png" alt="Groups" width="49%"></a>
|
||||||
<a href="images/oauth-clients.png" target="_blank"><img src="images/oauth-clients.png" alt="OAuth clients" width="49%"></a>
|
<a href="images/directory.png" target="_blank"><img src="images/directory.png" alt="Directory & inventory" width="49%"></a>
|
||||||
|
<a href="images/oauth-clients.png" target="_blank"><img src="images/oauth-clients.png" alt="OAuth client (edit view)" width="49%"></a>
|
||||||
|
|
||||||
*(click any screenshot to view full size)*
|
*(click any screenshot to view full size)*
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ Clients are managed directly from the **Directory** tab in the web UI. They are
|
|||||||
|
|
||||||
> All client-management actions use the standard Directory API (`/api/directory-admin/resources`) and are gated by the `app_sso_directory_admin` group.
|
> All client-management actions use the standard Directory API (`/api/directory-admin/resources`) and are gated by the `app_sso_directory_admin` group.
|
||||||
|
|
||||||
|
<a href="images/oauth-clients.png" target="_blank"><img src="images/oauth-clients.png" alt="Editing an OAuth client resource" width="80%"></a>
|
||||||
|
|
||||||
## Scopes
|
## Scopes
|
||||||
|
|
||||||
| Scope | Claims / access |
|
| Scope | Claims / access |
|
||||||
|
|||||||
@@ -473,6 +473,19 @@ User.update = async function(data){
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(data.sshPublicKey){
|
if(data.sshPublicKey){
|
||||||
|
// Ensure the auxiliary objectClass is present before setting the attribute
|
||||||
|
// -- accounts created before ldapPublicKey was added to addPosixAccount's
|
||||||
|
// objectclass list (e.g. the bootstrap admin) won't have it yet.
|
||||||
|
try {
|
||||||
|
await client.modify(this.dn, [
|
||||||
|
new Change({
|
||||||
|
operation: 'add',
|
||||||
|
modification: new Attribute({ type: 'objectClass', values: ['ldapPublicKey'] }),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
} catch(e) {
|
||||||
|
if(e.name !== 'TypeOrValueExistsError') throw e;
|
||||||
|
}
|
||||||
await client.modify(this.dn, [
|
await client.modify(this.dn, [
|
||||||
new Change({
|
new Change({
|
||||||
operation: 'replace',
|
operation: 'replace',
|
||||||
@@ -784,6 +797,19 @@ User.addSSHkey = async function(data) {
|
|||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
await withClient(async (client) => {
|
await withClient(async (client) => {
|
||||||
|
// Ensure the auxiliary objectClass is present before setting the attribute
|
||||||
|
// -- accounts created before ldapPublicKey was added to addPosixAccount's
|
||||||
|
// objectclass list (e.g. the bootstrap admin) won't have it yet.
|
||||||
|
try {
|
||||||
|
await client.modify(user.dn, [
|
||||||
|
new Change({
|
||||||
|
operation: 'add',
|
||||||
|
modification: new Attribute({ type: 'objectClass', values: ['ldapPublicKey'] }),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
} catch(e) {
|
||||||
|
if (e.name !== 'TypeOrValueExistsError') throw e;
|
||||||
|
}
|
||||||
await client.modify(user.dn, [
|
await client.modify(user.dn, [
|
||||||
new Change({
|
new Change({
|
||||||
operation: 'add',
|
operation: 'add',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.5.0",
|
"version": "1.5.1",
|
||||||
"description": "A very simple LDAP management and SSO system",
|
"description": "A very simple LDAP management and SSO system",
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -572,6 +572,8 @@
|
|||||||
$target.append($('<option>').val(r.id).text(r.name + ' (' + r.slug + ')'));
|
$target.append($('<option>').val(r.id).text(r.name + ' (' + r.slug + ')'));
|
||||||
} else if (kind === 'service' && (r.kind === 'host' || r.kind === 'service')) {
|
} else if (kind === 'service' && (r.kind === 'host' || r.kind === 'service')) {
|
||||||
$target.append($('<option>').val(r.id).text(r.name + ' (' + r.slug + ')'));
|
$target.append($('<option>').val(r.id).text(r.name + ' (' + r.slug + ')'));
|
||||||
|
} else if (kind === 'oauth' && r.kind === 'service') {
|
||||||
|
$target.append($('<option>').val(r.id).text(r.name + ' (' + r.slug + ')'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (selectedId) $target.val(selectedId);
|
if (selectedId) $target.val(selectedId);
|
||||||
|
|||||||