@shamar/adonis 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -2
- package/assets/admin.css +1 -1
- package/assets/shamar-ui.js +290 -5
- package/dist/auth/ldap.d.ts +115 -0
- package/dist/auth/ldap.d.ts.map +1 -0
- package/dist/auth/ldap.js +275 -0
- package/dist/auth/ldap.js.map +1 -0
- package/dist/auth/masquerade.d.ts +31 -0
- package/dist/auth/masquerade.d.ts.map +1 -0
- package/dist/auth/masquerade.js +36 -0
- package/dist/auth/masquerade.js.map +1 -0
- package/dist/auth_login_view.d.ts +56 -0
- package/dist/auth_login_view.d.ts.map +1 -0
- package/dist/auth_login_view.js +56 -0
- package/dist/auth_login_view.js.map +1 -0
- package/dist/commands/commands.json +1 -0
- package/dist/commands/main.d.ts +4 -0
- package/dist/commands/main.js +36 -0
- package/dist/commands/publish_auth.d.ts +13 -0
- package/dist/commands/publish_auth.d.ts.map +1 -0
- package/dist/commands/publish_auth.js +26 -0
- package/dist/commands/publish_auth.js.map +1 -0
- package/dist/config.d.ts +59 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts +16 -1
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +6 -0
- package/dist/configure.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/controllers/admin_controller.d.ts +1 -0
- package/dist/controllers/admin_controller.d.ts.map +1 -1
- package/dist/controllers/admin_controller.js +91 -9
- package/dist/controllers/admin_controller.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +2 -1
- package/dist/provider.js.map +1 -1
- package/dist/publish_auth.d.ts +45 -0
- package/dist/publish_auth.d.ts.map +1 -0
- package/dist/publish_auth.js +69 -0
- package/dist/publish_auth.js.map +1 -0
- package/dist/resources/api_key_resource.d.ts +4 -6
- package/dist/resources/api_key_resource.d.ts.map +1 -1
- package/dist/resources/api_key_resource.js +4 -16
- package/dist/resources/api_key_resource.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +27 -5
- package/dist/routes.js.map +1 -1
- package/dist/shamar/auth.d.ts +13 -1
- package/dist/shamar/auth.d.ts.map +1 -1
- package/dist/shamar/auth.js +42 -5
- package/dist/shamar/auth.js.map +1 -1
- package/dist/shamar/list-query.d.ts +9 -0
- package/dist/shamar/list-query.d.ts.map +1 -1
- package/dist/shamar/list-query.js +29 -1
- package/dist/shamar/list-query.js.map +1 -1
- package/dist/shamar/relation-fields.d.ts +15 -0
- package/dist/shamar/relation-fields.d.ts.map +1 -1
- package/dist/shamar/relation-fields.js +25 -4
- package/dist/shamar/relation-fields.js.map +1 -1
- package/dist/shamar/relation-table.d.ts +37 -0
- package/dist/shamar/relation-table.d.ts.map +1 -0
- package/dist/shamar/relation-table.js +61 -0
- package/dist/shamar/relation-table.js.map +1 -0
- package/dist/shamar/view-context.d.ts +8 -0
- package/dist/shamar/view-context.d.ts.map +1 -1
- package/dist/shamar/view-context.js +1 -0
- package/dist/shamar/view-context.js.map +1 -1
- package/package.json +15 -6
- package/resources/views/shamar/partials/detail-entry.edge +7 -0
- package/resources/views/shamar/partials/field-relation-table.edge +309 -105
- package/resources/views/shamar/partials/list-row.edge +17 -4
- package/resources/views/shamar/partials/nav.edge +6 -1
- package/resources/views/shamar/partials/shell-open.edge +14 -0
- package/resources/views/shamar/partials/topbar.edge +3 -0
- package/resources/views/shamar/show.edge +59 -20
- package/stubs/views/auth/layout.edge +244 -0
- package/stubs/views/auth/login.edge +65 -0
package/assets/shamar-ui.js
CHANGED
|
@@ -818,9 +818,12 @@
|
|
|
818
818
|
|
|
819
819
|
openRecord() {
|
|
820
820
|
if (!this.value || !this.detailUrlBase) return;
|
|
821
|
+
const url = this.readonly
|
|
822
|
+
? `${this.detailUrlBase}/${this.value}`
|
|
823
|
+
: `${this.detailUrlBase}/${this.value}/edit`;
|
|
821
824
|
window.ShamarUI.openDialog({
|
|
822
|
-
url
|
|
823
|
-
title: this.label || 'Edit record',
|
|
825
|
+
url,
|
|
826
|
+
title: this.label || (this.readonly ? 'View record' : 'Edit record'),
|
|
824
827
|
slug: this.relatedResource,
|
|
825
828
|
});
|
|
826
829
|
},
|
|
@@ -1119,9 +1122,12 @@
|
|
|
1119
1122
|
|
|
1120
1123
|
openRecord(item) {
|
|
1121
1124
|
if (!item?.id || !this.detailUrlBase) return;
|
|
1125
|
+
const url = this.readonly
|
|
1126
|
+
? `${this.detailUrlBase}/${item.id}`
|
|
1127
|
+
: `${this.detailUrlBase}/${item.id}/edit`;
|
|
1122
1128
|
window.ShamarUI.openDialog({
|
|
1123
|
-
url
|
|
1124
|
-
title: item.label || 'Edit record',
|
|
1129
|
+
url,
|
|
1130
|
+
title: item.label || (this.readonly ? 'View record' : 'Edit record'),
|
|
1125
1131
|
slug: this.relatedResource,
|
|
1126
1132
|
});
|
|
1127
1133
|
},
|
|
@@ -1453,10 +1459,30 @@
|
|
|
1453
1459
|
function createShamarM2mTable(cfg) {
|
|
1454
1460
|
const self = createShamarM2m(cfg);
|
|
1455
1461
|
self.addOpen = false;
|
|
1462
|
+
self.listUrl = cfg.listUrl || null;
|
|
1463
|
+
self.columns = Array.isArray(cfg.columns) ? cfg.columns : [];
|
|
1464
|
+
self.listHeaders = Array.isArray(cfg.listHeaders) ? cfg.listHeaders : [];
|
|
1465
|
+
self.rows = [];
|
|
1466
|
+
self.total = 0;
|
|
1467
|
+
self.page = 1;
|
|
1468
|
+
self.pageCount = 1;
|
|
1469
|
+
self.perPage = String(cfg.perPage || 10);
|
|
1470
|
+
self.listSearch = '';
|
|
1471
|
+
self.listFilters = [];
|
|
1472
|
+
self.listSort = cfg.defaultSort || '';
|
|
1473
|
+
self.listDirection = cfg.defaultDirection || 'asc';
|
|
1474
|
+
self.listLoading = false;
|
|
1475
|
+
self.filterPanelOpen = false;
|
|
1476
|
+
self._listAbort = null;
|
|
1477
|
+
|
|
1456
1478
|
const prevInit = self.init;
|
|
1457
1479
|
const prevDestroy = self.destroy;
|
|
1458
1480
|
const prevPick = self.pick;
|
|
1459
1481
|
const prevCreateAndEdit = self.createAndEdit;
|
|
1482
|
+
const prevAttach = self.attachHasMany;
|
|
1483
|
+
const prevDetach = self.detachHasMany;
|
|
1484
|
+
const prevOpenRecord = self.openRecord;
|
|
1485
|
+
|
|
1460
1486
|
self.init = function initTable() {
|
|
1461
1487
|
prevInit.call(this);
|
|
1462
1488
|
this._tablePickHandler = (event) => {
|
|
@@ -1467,13 +1493,190 @@
|
|
|
1467
1493
|
}
|
|
1468
1494
|
};
|
|
1469
1495
|
window.addEventListener('shamar-m2m-pick', this._tablePickHandler);
|
|
1496
|
+
if (this.listUrl) {
|
|
1497
|
+
this.loadList();
|
|
1498
|
+
}
|
|
1470
1499
|
};
|
|
1500
|
+
|
|
1471
1501
|
self.destroy = function destroyTable() {
|
|
1472
1502
|
prevDestroy.call(this);
|
|
1473
1503
|
if (this._tablePickHandler) {
|
|
1474
1504
|
window.removeEventListener('shamar-m2m-pick', this._tablePickHandler);
|
|
1475
1505
|
}
|
|
1506
|
+
if (this._listAbort) this._listAbort.abort();
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
Object.defineProperty(self, 'filterableHeaders', {
|
|
1510
|
+
get() {
|
|
1511
|
+
return (this.listHeaders || []).filter(
|
|
1512
|
+
(header) =>
|
|
1513
|
+
header.filter_kind === 'boolean' ||
|
|
1514
|
+
(header.filter_kind === 'select' && Array.isArray(header.options)),
|
|
1515
|
+
);
|
|
1516
|
+
},
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
Object.defineProperty(self, 'availableResults', {
|
|
1520
|
+
get() {
|
|
1521
|
+
const linked = new Set([
|
|
1522
|
+
...(this.selected || []).map((item) => String(item.id)),
|
|
1523
|
+
...(this.rows || []).map((row) => String(row.id)),
|
|
1524
|
+
]);
|
|
1525
|
+
return (this.results || []).filter((item) => !linked.has(String(item.id)));
|
|
1526
|
+
},
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
self.cellAlignment = function cellAlignment(columnName) {
|
|
1530
|
+
const column = (this.columns || []).find((entry) => entry.name === columnName);
|
|
1531
|
+
return column?.alignmentClass || '';
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
self.buildListUrl = function buildListUrl() {
|
|
1535
|
+
const url = new URL(this.listUrl, window.location.origin);
|
|
1536
|
+
if (this.listSearch) url.searchParams.set('search', this.listSearch);
|
|
1537
|
+
if (this.listSort) {
|
|
1538
|
+
url.searchParams.set('sort', this.listSort);
|
|
1539
|
+
if (this.listDirection === 'asc' || this.listDirection === 'desc') {
|
|
1540
|
+
url.searchParams.set('direction', this.listDirection);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
url.searchParams.set('page', String(this.page || 1));
|
|
1544
|
+
url.searchParams.set('perPage', String(this.perPage || 10));
|
|
1545
|
+
if (this.listFilters?.length) {
|
|
1546
|
+
url.searchParams.set('filters', JSON.stringify(this.listFilters));
|
|
1547
|
+
}
|
|
1548
|
+
return `${url.pathname}${url.search}`;
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
self.loadList = async function loadList() {
|
|
1552
|
+
if (!this.listUrl) return;
|
|
1553
|
+
if (this._listAbort) this._listAbort.abort();
|
|
1554
|
+
const ctl = new AbortController();
|
|
1555
|
+
this._listAbort = ctl;
|
|
1556
|
+
this.listLoading = true;
|
|
1557
|
+
try {
|
|
1558
|
+
const response = await fetch(this.buildListUrl(), {
|
|
1559
|
+
signal: ctl.signal,
|
|
1560
|
+
credentials: 'same-origin',
|
|
1561
|
+
headers: { Accept: 'application/json' },
|
|
1562
|
+
});
|
|
1563
|
+
if (!response.ok) throw new Error('list failed');
|
|
1564
|
+
const data = await response.json();
|
|
1565
|
+
this.rows = Array.isArray(data.rows) ? data.rows : [];
|
|
1566
|
+
this.total = Number(data.total) || 0;
|
|
1567
|
+
this.page = Number(data.page) || 1;
|
|
1568
|
+
this.pageCount = Math.max(1, Number(data.pageCount) || 1);
|
|
1569
|
+
this.perPage = String(data.perPage || this.perPage || 10);
|
|
1570
|
+
if (Array.isArray(data.columns) && data.columns.length) {
|
|
1571
|
+
this.columns = data.columns;
|
|
1572
|
+
}
|
|
1573
|
+
if (Array.isArray(data.listHeaders)) {
|
|
1574
|
+
this.listHeaders = data.listHeaders;
|
|
1575
|
+
}
|
|
1576
|
+
if (data.sort) this.listSort = data.sort;
|
|
1577
|
+
if (data.direction) this.listDirection = data.direction;
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
if (error.name !== 'AbortError') {
|
|
1580
|
+
this.rows = [];
|
|
1581
|
+
this.total = 0;
|
|
1582
|
+
showToast('error', {
|
|
1583
|
+
title: 'Error',
|
|
1584
|
+
message: 'Unable to load related records.',
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
} finally {
|
|
1588
|
+
this.listLoading = false;
|
|
1589
|
+
}
|
|
1476
1590
|
};
|
|
1591
|
+
|
|
1592
|
+
self.applyListSearch = function applyListSearch() {
|
|
1593
|
+
this.page = 1;
|
|
1594
|
+
this.loadList();
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
self.setListPage = function setListPage(page) {
|
|
1598
|
+
const next = Math.max(1, Number(page) || 1);
|
|
1599
|
+
this.page = next;
|
|
1600
|
+
this.loadList();
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
self.toggleListSort = function toggleListSort(field) {
|
|
1604
|
+
if (!field) return;
|
|
1605
|
+
if (this.listSort === field) {
|
|
1606
|
+
this.listDirection = this.listDirection === 'asc' ? 'desc' : 'asc';
|
|
1607
|
+
} else {
|
|
1608
|
+
this.listSort = field;
|
|
1609
|
+
this.listDirection = 'asc';
|
|
1610
|
+
}
|
|
1611
|
+
this.page = 1;
|
|
1612
|
+
this.loadList();
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
self.hasFilter = function hasFilter(header, value) {
|
|
1616
|
+
const field = header.filterField || header.name;
|
|
1617
|
+
return this.listFilters.some(
|
|
1618
|
+
(chip) => chip.field === field && String(chip.value) === String(value),
|
|
1619
|
+
);
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
self.toggleBooleanFilter = function toggleBooleanFilter(header, value) {
|
|
1623
|
+
const field = header.filterField || header.name;
|
|
1624
|
+
const existing = this.listFilters.find((chip) => chip.field === field);
|
|
1625
|
+
if (existing && String(existing.value) === String(value)) {
|
|
1626
|
+
this.listFilters = this.listFilters.filter((chip) => chip.field !== field);
|
|
1627
|
+
} else {
|
|
1628
|
+
this.listFilters = [
|
|
1629
|
+
...this.listFilters.filter((chip) => chip.field !== field),
|
|
1630
|
+
{
|
|
1631
|
+
field,
|
|
1632
|
+
op: '=',
|
|
1633
|
+
value,
|
|
1634
|
+
label: `${header.label}: ${value ? 'Yes' : 'No'}`,
|
|
1635
|
+
},
|
|
1636
|
+
];
|
|
1637
|
+
}
|
|
1638
|
+
this.page = 1;
|
|
1639
|
+
this.loadList();
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
self.toggleSelectFilter = function toggleSelectFilter(header, opt) {
|
|
1643
|
+
const field = header.filterField || header.name;
|
|
1644
|
+
const value = opt.value;
|
|
1645
|
+
if (this.hasFilter(header, value)) {
|
|
1646
|
+
this.listFilters = this.listFilters.filter(
|
|
1647
|
+
(chip) => !(chip.field === field && String(chip.value) === String(value)),
|
|
1648
|
+
);
|
|
1649
|
+
} else {
|
|
1650
|
+
this.listFilters = [
|
|
1651
|
+
...this.listFilters.filter((chip) => chip.field !== field),
|
|
1652
|
+
{
|
|
1653
|
+
field,
|
|
1654
|
+
op: '=',
|
|
1655
|
+
value,
|
|
1656
|
+
label: `${header.label}: ${opt.label}`,
|
|
1657
|
+
},
|
|
1658
|
+
];
|
|
1659
|
+
}
|
|
1660
|
+
this.page = 1;
|
|
1661
|
+
this.loadList();
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
self.removeListFilter = function removeListFilter(chip) {
|
|
1665
|
+
this.listFilters = this.listFilters.filter(
|
|
1666
|
+
(entry) =>
|
|
1667
|
+
!(entry.field === chip.field && String(entry.value) === String(chip.value)),
|
|
1668
|
+
);
|
|
1669
|
+
this.page = 1;
|
|
1670
|
+
this.loadList();
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
self.clearListFilters = function clearListFilters() {
|
|
1674
|
+
this.listFilters = [];
|
|
1675
|
+
this.filterPanelOpen = false;
|
|
1676
|
+
this.page = 1;
|
|
1677
|
+
this.loadList();
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1477
1680
|
self.toggleAdd = function toggleAdd() {
|
|
1478
1681
|
this.addOpen = !this.addOpen;
|
|
1479
1682
|
if (this.addOpen) {
|
|
@@ -1482,18 +1685,76 @@
|
|
|
1482
1685
|
this.$nextTick?.(() => this.$refs.addInput?.focus());
|
|
1483
1686
|
}
|
|
1484
1687
|
};
|
|
1688
|
+
|
|
1485
1689
|
self.closeAdd = function closeAdd() {
|
|
1486
1690
|
this.addOpen = false;
|
|
1487
1691
|
this.query = '';
|
|
1488
1692
|
};
|
|
1693
|
+
|
|
1489
1694
|
self.pick = function pickTable(item) {
|
|
1695
|
+
if (
|
|
1696
|
+
this.listUrl &&
|
|
1697
|
+
item?.id != null &&
|
|
1698
|
+
this.rows.some((row) => String(row.id) === String(item.id))
|
|
1699
|
+
) {
|
|
1700
|
+
this.closeAdd();
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1490
1703
|
prevPick.call(this, item);
|
|
1491
1704
|
this.closeAdd();
|
|
1492
1705
|
};
|
|
1706
|
+
|
|
1707
|
+
self.attachHasMany = async function attachHasManyTable(item) {
|
|
1708
|
+
await prevAttach.call(this, item);
|
|
1709
|
+
if (this.listUrl) {
|
|
1710
|
+
this.page = 1;
|
|
1711
|
+
await this.loadList();
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
self.detachHasMany = async function detachHasManyTable(relatedId) {
|
|
1716
|
+
await prevDetach.call(this, relatedId);
|
|
1717
|
+
if (this.listUrl) {
|
|
1718
|
+
await this.loadList();
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
self.openRecord = function openRecordTable(item) {
|
|
1723
|
+
if (!item?.id || !this.detailUrlBase) return;
|
|
1724
|
+
const url = this.readonly
|
|
1725
|
+
? `${this.detailUrlBase}/${item.id}`
|
|
1726
|
+
: `${this.detailUrlBase}/${item.id}/edit`;
|
|
1727
|
+
window.ShamarUI.openDialog({
|
|
1728
|
+
url,
|
|
1729
|
+
title: item.label || (this.readonly ? 'View record' : 'Edit record'),
|
|
1730
|
+
slug: this.relatedResource,
|
|
1731
|
+
onResult: () => {
|
|
1732
|
+
if (this.listUrl) this.loadList();
|
|
1733
|
+
},
|
|
1734
|
+
});
|
|
1735
|
+
};
|
|
1736
|
+
|
|
1493
1737
|
self.createAndEdit = function createAndEditTable() {
|
|
1494
1738
|
this.closeAdd();
|
|
1495
|
-
|
|
1739
|
+
if (!this.createUrl) return;
|
|
1740
|
+
const q = (this.query || '').trim();
|
|
1741
|
+
const url = q
|
|
1742
|
+
? `${this.createUrl}${this.createUrl.includes('?') ? '&' : '?'}name=${encodeURIComponent(q)}`
|
|
1743
|
+
: this.createUrl;
|
|
1744
|
+
const title = q ? `New ${this.singularLabel}` : `Create ${this.singularLabel}`;
|
|
1745
|
+
window.ShamarUI.openDialog({
|
|
1746
|
+
url,
|
|
1747
|
+
title,
|
|
1748
|
+
slug: this.relatedResource,
|
|
1749
|
+
onResult: (detail) => {
|
|
1750
|
+
if (detail?.id) {
|
|
1751
|
+
prevPick.call(this, { id: detail.id, label: detail.label });
|
|
1752
|
+
}
|
|
1753
|
+
if (this.listUrl) this.loadList();
|
|
1754
|
+
},
|
|
1755
|
+
});
|
|
1496
1756
|
};
|
|
1757
|
+
|
|
1497
1758
|
return self;
|
|
1498
1759
|
}
|
|
1499
1760
|
|
|
@@ -3134,12 +3395,36 @@
|
|
|
3134
3395
|
applyListHrefs();
|
|
3135
3396
|
bindListViewSwitcher();
|
|
3136
3397
|
bindListRefresh();
|
|
3398
|
+
bindListRowNavigation();
|
|
3137
3399
|
bindMediaUploads();
|
|
3138
3400
|
bindFormSaveShortcut();
|
|
3139
3401
|
bindFormAutosave();
|
|
3140
3402
|
bindRecordPagerNav();
|
|
3141
3403
|
});
|
|
3142
3404
|
|
|
3405
|
+
function bindListRowNavigation() {
|
|
3406
|
+
document.addEventListener('click', (event) => {
|
|
3407
|
+
if (!(event instanceof MouseEvent) || event.button !== 0) return;
|
|
3408
|
+
if (event.defaultPrevented) return;
|
|
3409
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3410
|
+
const target = event.target;
|
|
3411
|
+
if (!(target instanceof Element)) return;
|
|
3412
|
+
if (
|
|
3413
|
+
target.closest(
|
|
3414
|
+
'a, button, input, select, textarea, label, [data-shamar-stop-row-nav]',
|
|
3415
|
+
)
|
|
3416
|
+
) {
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
const row = target.closest('tr[data-shamar-row-href]');
|
|
3420
|
+
if (!row) return;
|
|
3421
|
+
const href = row.getAttribute('data-shamar-row-href');
|
|
3422
|
+
if (!href) return;
|
|
3423
|
+
event.preventDefault();
|
|
3424
|
+
window.location.assign(href);
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3143
3428
|
function relationConfigsFromForm(form) {
|
|
3144
3429
|
const configs = [];
|
|
3145
3430
|
if (!(form instanceof HTMLFormElement)) return configs;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type AuthLoginMode, type ExternalIdentity } from '@shamar/cherubim';
|
|
2
|
+
export interface LdapDomainConfig {
|
|
3
|
+
/** Stable key, e.g. corp. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Human label for logs / UI. */
|
|
6
|
+
label?: string;
|
|
7
|
+
/** LDAP(S) URL. */
|
|
8
|
+
url: string;
|
|
9
|
+
/** Optional service-account bind for search. */
|
|
10
|
+
bindDn?: string;
|
|
11
|
+
bindPassword?: string;
|
|
12
|
+
searchBase: string;
|
|
13
|
+
/** Filter with {{username}} placeholder. */
|
|
14
|
+
searchFilter: string;
|
|
15
|
+
emailAttribute?: string;
|
|
16
|
+
nameAttribute?: string;
|
|
17
|
+
/** Default memberOf. */
|
|
18
|
+
groupsAttribute?: string;
|
|
19
|
+
groupRoleMap?: Record<string, string>;
|
|
20
|
+
/** Prefer this domain when login uses an email domain suffix. */
|
|
21
|
+
emailDomains?: string[];
|
|
22
|
+
/** Prefer this domain for NetBIOS-prefixed logins. */
|
|
23
|
+
netbios?: string;
|
|
24
|
+
tls?: {
|
|
25
|
+
rejectUnauthorized?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/** Operation timeout in milliseconds (ldapts `timeout`). Default 10_000. */
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
/** TCP connect timeout in milliseconds (ldapts `connectTimeout`). Defaults to `timeoutMs`. */
|
|
30
|
+
connectTimeoutMs?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface LdapAuthSettings {
|
|
33
|
+
domains: LdapDomainConfig[];
|
|
34
|
+
/**
|
|
35
|
+
* How to link a successful LDAP bind to a local user:
|
|
36
|
+
* - `existing` (default) — only log in if a local row already has the sync `externalId`
|
|
37
|
+
* - `create` — upsert a local user when missing (auto-provision)
|
|
38
|
+
*/
|
|
39
|
+
provisioning?: LdapProvisioningMode;
|
|
40
|
+
}
|
|
41
|
+
/** Local user must pre-exist (`existing`) or may be auto-created (`create`). */
|
|
42
|
+
export type LdapProvisioningMode = 'existing' | 'create';
|
|
43
|
+
/** Pluggable directory client — production uses ldapts; tests inject a mock. */
|
|
44
|
+
export interface LdapDirectoryClient {
|
|
45
|
+
authenticate(username: string, password: string, domain: LdapDomainConfig): Promise<ExternalIdentity | null>;
|
|
46
|
+
}
|
|
47
|
+
export type LocalCredentialVerifier<TUser> = (username: string, password: string) => Promise<TUser | null>;
|
|
48
|
+
export type LdapUserLinker<TUser> = (identity: ExternalIdentity) => Promise<TUser | null>;
|
|
49
|
+
export type PasswordLoginSuccess<TUser> = {
|
|
50
|
+
ok: true;
|
|
51
|
+
user: TUser;
|
|
52
|
+
method: 'ldap' | 'local';
|
|
53
|
+
};
|
|
54
|
+
export type PasswordLoginFailure = {
|
|
55
|
+
ok: false;
|
|
56
|
+
message: string;
|
|
57
|
+
/** Set when directory contact timed out. */
|
|
58
|
+
code?: 'ldap_timeout';
|
|
59
|
+
};
|
|
60
|
+
export type PasswordLoginResult<TUser> = PasswordLoginSuccess<TUser> | PasswordLoginFailure;
|
|
61
|
+
export declare const LDAP_TIMEOUT_MESSAGE = "Directory server timed out. Please try again.";
|
|
62
|
+
/** Thrown / rethrown when ldapts (or the network) times out contacting a domain. */
|
|
63
|
+
export declare class LdapTimeoutError extends Error {
|
|
64
|
+
readonly domainId: string;
|
|
65
|
+
constructor(domainId: string, cause?: unknown);
|
|
66
|
+
}
|
|
67
|
+
export declare function isLdapTimeoutError(error: unknown): boolean;
|
|
68
|
+
export type AuthenticateLdapResult = {
|
|
69
|
+
identity: ExternalIdentity | null;
|
|
70
|
+
/** True when at least one domain attempt failed due to a timeout. */
|
|
71
|
+
timedOut: boolean;
|
|
72
|
+
};
|
|
73
|
+
export declare function resolveLdapProvisioning(mode: LdapProvisioningMode | undefined): LdapProvisioningMode;
|
|
74
|
+
export declare function resolveAuthLoginMode(mode: AuthLoginMode | undefined, domains: LdapDomainConfig[] | undefined): AuthLoginMode;
|
|
75
|
+
/**
|
|
76
|
+
* Order domains: hinted matches first, then remaining config order.
|
|
77
|
+
*/
|
|
78
|
+
export declare function orderLdapDomains(usernameInput: string, domains: LdapDomainConfig[]): LdapDomainConfig[];
|
|
79
|
+
export declare function searchUsernameForDomain(usernameInput: string, _domain: LdapDomainConfig): string;
|
|
80
|
+
/**
|
|
81
|
+
* Try domains in order until one authenticates.
|
|
82
|
+
* Returns `{ identity: null }` when none succeed (caller decides local fallback).
|
|
83
|
+
* Sets `timedOut` when any domain attempt hit a connect/operation timeout.
|
|
84
|
+
*/
|
|
85
|
+
export declare function authenticateLdap(usernameInput: string, password: string, domains: LdapDomainConfig[], client: LdapDirectoryClient): Promise<AuthenticateLdapResult>;
|
|
86
|
+
/**
|
|
87
|
+
* Orchestrate local / LDAP / both login modes.
|
|
88
|
+
*
|
|
89
|
+
* After a successful directory bind, {@link linkFromLdap} must resolve a local
|
|
90
|
+
* user (matched by sync UID / externalId). When `provisioning` is `existing`
|
|
91
|
+
* and no local row exists, LDAP login fails (or falls through to local when
|
|
92
|
+
* `loginMode` is `both`).
|
|
93
|
+
*/
|
|
94
|
+
export declare function resolvePasswordLogin<TUser>(options: {
|
|
95
|
+
username: string;
|
|
96
|
+
password: string;
|
|
97
|
+
loginMode?: AuthLoginMode;
|
|
98
|
+
domains?: LdapDomainConfig[];
|
|
99
|
+
/** Default `existing` — AD users must already exist locally with a sync UID. */
|
|
100
|
+
provisioning?: LdapProvisioningMode;
|
|
101
|
+
client: LdapDirectoryClient;
|
|
102
|
+
verifyLocal: LocalCredentialVerifier<TUser>;
|
|
103
|
+
/**
|
|
104
|
+
* Link LDAP identity → local user.
|
|
105
|
+
* Return `null` when no local user exists (and auto-create is not desired).
|
|
106
|
+
*/
|
|
107
|
+
linkFromLdap: LdapUserLinker<TUser>;
|
|
108
|
+
}): Promise<PasswordLoginResult<TUser>>;
|
|
109
|
+
/** Apply domain groupRoleMap to an identity's groups. */
|
|
110
|
+
export declare function roleIdsFromLdapIdentity(identity: ExternalIdentity, domain: LdapDomainConfig): string[];
|
|
111
|
+
/**
|
|
112
|
+
* Default ldapts-backed client. Requires the optional `ldapts` peer dependency.
|
|
113
|
+
*/
|
|
114
|
+
export declare function createLdaptsDirectoryClient(): LdapDirectoryClient;
|
|
115
|
+
//# sourceMappingURL=ldap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ldap.d.ts","sourceRoot":"","sources":["../../src/auth/ldap.ts"],"names":[],"mappings":"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"}
|