Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
600eed6
feat(auth): support parent-based areaRestrictions in config
unseenmagik Mar 19, 2026
37cd8ad
fix(areas): include parent keys in grouped child selection
unseenmagik Mar 20, 2026
8a136a0
fix(areas): normalize parent restrictions to area keys
Mygod Mar 26, 2026
f3794da
fix(areas): keep parent rows aligned with visible permissions
Mygod Mar 26, 2026
02f41c3
fix(areas): handle header-only parents and domain scoping
Mygod Mar 26, 2026
de39a54
Merge branch 'develop' into main
Mygod Mar 26, 2026
1ffbdfd
fix(areas): tighten parent restriction expansion
Mygod Mar 26, 2026
f7562fa
fix(areas): preserve parent group access
Mygod Mar 26, 2026
889b852
fix(areas): tighten parent filter keys
Mygod Mar 26, 2026
c557395
fix(auth): close parent area auth gaps
Mygod Mar 27, 2026
6038634
fix(areas): harden mem filters and parent rows
Mygod Mar 27, 2026
1185cbc
fix(areas): restore legacy parent behavior
Mygod Mar 27, 2026
17b0beb
fix(areas): preserve parent rollout access
Mygod Mar 27, 2026
400ab95
fix(areas): restore unrestricted scan area access
Mygod Mar 27, 2026
1bbcaf5
fix(auth): distinguish unrestricted area grants
Mygod Mar 27, 2026
1afbb31
fix(weather): honor no-access area restrictions
Mygod Mar 27, 2026
b5fb48e
fix(areas): preserve persisted child filters
Mygod Mar 27, 2026
28fcd46
fix(auth): normalize persisted area restrictions
Mygod Mar 27, 2026
9dab6da
fix(areas): scope filtered parent scan toggles
Mygod Mar 27, 2026
e65f4df
fix(auth): preserve unrestricted area merges
Mygod Mar 30, 2026
2803e05
fix(auth): preserve no-access normalization
Mygod Mar 30, 2026
f081a88
fix(auth): preserve scoped area restrictions
Mygod Mar 30, 2026
155040b
fix(auth): preserve legacy parent area grants
Mygod Mar 31, 2026
d210742
fix(auth): scope legacy parent fallback
Mygod Mar 31, 2026
ac88d2d
fix(auth): prefer concrete area names
Mygod Mar 31, 2026
f136ea6
fix(auth): resolve legacy area targets safely
Mygod Mar 31, 2026
591f718
fix(auth): avoid ambiguous parent expansion
Mygod Mar 31, 2026
5b84973
fix(drawer): include grouped parent area key
Mygod Mar 31, 2026
0df5be0
fix(drawer): keep grouped area toggles scoped
Mygod Mar 31, 2026
a980290
fix(auth): expand reused parent keys
Mygod Mar 31, 2026
5325f12
fix(drawer): preserve grouped area exclusions
Mygod Mar 31, 2026
7e3b1d5
fix(auth): scope parent grants per request
Mygod Mar 31, 2026
90d64e4
fix(drawer): preserve grouped search state
Mygod Mar 31, 2026
8af5dfc
fix(drawer): keep grouped toggles consistent
Mygod Mar 31, 2026
ce0ad44
fix(drawer): avoid parent area overreach
Mygod Mar 31, 2026
9e51d12
fix(drawer): clear legacy parent filters
Mygod Mar 31, 2026
8f4a99f
fix(auth): scope serialized parent grants
Mygod Mar 31, 2026
79e3a2d
fix(auth): preserve area restriction semantics
Mygod Mar 31, 2026
e9dc694
fix(auth): keep parent grants portable
Mygod Mar 31, 2026
8bc11e1
fix(auth): avoid parent key overreach
Mygod Mar 31, 2026
ae79c3e
fix(auth): scope named area rules
Mygod Mar 31, 2026
3e23d99
fix(auth): scope serialized parent grants
Mygod Mar 31, 2026
168edf5
fix(auth): preserve unrestricted area grants
Mygod Mar 31, 2026
aea4ec7
fix(drawer): exclude grouped parent filter keys
Mygod Mar 31, 2026
f2776a7
fix(auth): keep legacy area grants global
Mygod Mar 31, 2026
b6c863a
fix(drawer): allow manual-only parent filters
Mygod Mar 31, 2026
9052b70
fix(auth): scope named area grants
Mygod Mar 31, 2026
c8f4dcb
fix(auth): scope anonymous parent grants
Mygod Mar 31, 2026
85fd773
fix(drawer): keep map parent filters editable
Mygod Mar 31, 2026
1052c23
fix(drawer): keep child selection literal
Mygod Mar 31, 2026
d5e84b4
fix(drawer): normalize grouped map toggles
Mygod Mar 31, 2026
2723720
fix(scanArea): migrate legacy grouped filters
Mygod Mar 31, 2026
d966285
fix(scanArea): migrate filters outside the layer
Mygod Mar 31, 2026
68ed024
fix(areas): align legacy grouped selection state
Mygod Mar 31, 2026
2e0e698
fix(scanArea): preserve legacy child map toggles
Mygod Mar 31, 2026
70d0332
fix(scanArea): migrate legacy keys on hydration
Mygod Mar 31, 2026
f7c97c1
fix(scanArea): gate legacy filter migration
Mygod Mar 31, 2026
bfb73c6
fix(scanArea): hydrate legacy filters from config
Mygod Mar 31, 2026
b4ea431
fix(scanArea): preserve partial parent toggles
Mygod Mar 31, 2026
12de2fa
fix(scanArea): migrate legacy filters from settings
Mygod Mar 31, 2026
8496744
fix(scanArea): make partial parent taps additive
Mygod Mar 31, 2026
737058c
fix(auth): preserve no-access scan area settings
Mygod Mar 31, 2026
69f2d89
fix(scanArea): preserve legacy parent migration keys
Mygod Mar 31, 2026
9039b71
fix(scanArea): keep manual-only groups non-selectable
Mygod Mar 31, 2026
9a94336
fix(auth): scope persisted area grants
Mygod Mar 31, 2026
12fab73
fix(scanArea): restore grouped parent polygons
Mygod Mar 31, 2026
8a8f963
fix(auth): preserve global area grants
Mygod Mar 31, 2026
a4d5fa6
fix(scanArea): keep child polygons clickable
Mygod Mar 31, 2026
ae0f015
fix(scanArea): preserve parent area coverage
Mygod Mar 31, 2026
21baa5d
fix(scanArea): support keyless parent outlines
Mygod Mar 31, 2026
ef482a2
fix(scanArea): gate parent keys by accessible areas
Mygod Mar 31, 2026
eb3b37e
fix(scanArea): keep grouped selections child-only
Mygod Mar 31, 2026
391aa5b
fix(scanArea): align grouped area selections
Mygod Mar 31, 2026
2578848
fix(scanArea): gate grouped parent selections
Mygod Mar 31, 2026
28768a3
fix(scanArea): stabilize grouped drawer toggles
Mygod Mar 31, 2026
fa6ce45
fix(scanArea): scope grouped area filters
Mygod Mar 31, 2026
9a8256b
fix(scanArea): tighten filtered group scoping
Mygod Mar 31, 2026
490d1de
fix(scanArea): resolve scoped child polygons
Mygod Mar 31, 2026
ca2210b
fix(scanArea): preserve split child polygons
Mygod Mar 31, 2026
6c89563
fix(scanArea): scope consolidated area grants
Mygod Mar 31, 2026
34b51cd
fix(scanArea): restore duplicate area resolution
Mygod Mar 31, 2026
7967fc3
fix(scanArea): scope unrestricted area filters
Mygod Mar 31, 2026
3ddd67d
fix(scanArea): restrict direct area filters
Mygod Mar 31, 2026
d834f4b
fix(auth): scope permissive area access
Mygod Mar 31, 2026
9d58050
fix(auth): resolve hidden area grants
Mygod Mar 31, 2026
aeecc6e
fix(auth): avoid hidden area overreach
Mygod Mar 31, 2026
2806080
fix(auth): ignore hidden parent aliases
Mygod Mar 31, 2026
ca343af
fix(scanArea): honor parent key child access
Mygod Mar 31, 2026
a3ff43e
fix(auth): scope foreign area grants
Mygod Mar 31, 2026
4e5c5c6
fix(auth): avoid foreign area alias expansion
Mygod Mar 31, 2026
50f672a
fix(auth): preserve scoped parent grants
Mygod Mar 31, 2026
cb7bf82
fix(auth): retain scoped grant resolutions
Mygod Mar 31, 2026
e912609
fix(auth): re-resolve scoped grant lookups
Mygod Mar 31, 2026
f2dd0f1
fix(auth): isolate key-only scoped lookups
Mygod Mar 31, 2026
98f2f5c
fix(auth): preserve global area grants
Mygod Mar 31, 2026
5f33ca3
fix(auth): scope resolved label grants
Mygod Mar 31, 2026
58023a0
fix(auth): refresh scoped area lookups
Mygod Mar 31, 2026
a9c100d
fix(auth): defer area normalization to requests
Mygod Mar 31, 2026
4d3a924
fix(auth): scope legacy area keys
Mygod Mar 31, 2026
8cfadae
fix(auth): limit legacy area scoping
Mygod Mar 31, 2026
7d5f80b
fix(auth): preserve legacy area scope
Mygod Mar 31, 2026
9db88e4
fix(auth): refine legacy scope recovery
Mygod Mar 31, 2026
f8c1b85
fix(auth): constrain legacy scope inference
Mygod Mar 31, 2026
eda4309
fix(auth): drop legacy area recovery
Mygod Mar 31, 2026
dcc6a40
refactor(auth): drop stale area compat
Mygod Mar 31, 2026
4df828c
refactor(scanArea): drop grouped parent toggles
Mygod Mar 31, 2026
f037f3d
refactor(areas): drop noncanonical access fallbacks
Mygod Mar 31, 2026
5722fc8
fix(auth): preserve grouped area grants
Mygod Mar 31, 2026
cfe2d2d
fix(scanArea): keep jump search loading stable
Mygod Mar 31, 2026
addcd2d
fix(auth): rescope persisted area grants
Mygod Apr 1, 2026
ba80a9b
fix(auth): rescope grouped area grants
Mygod Apr 1, 2026
35096b6
fix(scanArea): restore grouped parent selection
Mygod Apr 1, 2026
b3b0243
fix(scanArea): keep grouped selections refinable
Mygod Apr 1, 2026
478d1ea
fix(auth): keep bare area grants request scoped
Mygod Apr 1, 2026
957508b
fix(scanArea): keep manual children on allowed parents
Mygod Apr 1, 2026
004d62d
fix(scanArea): keep parent-only grants non-selectable
Mygod Apr 1, 2026
b8820ed
fix: remove unnecessary bluff
Mygod Apr 1, 2026
13409c0
fix(auth): preserve direct and linked area grants
Mygod Apr 1, 2026
5a1347c
fix(auth): refresh linked and grouped area grants
Mygod Apr 1, 2026
2730e16
fix(auth): scope live linked refreshes safely
Mygod Apr 1, 2026
d479498
fix(auth): persist refreshed linked snapshots
Mygod Apr 1, 2026
14d8951
fix(auth): surface local denials safely
Mygod Apr 1, 2026
d3a1028
fix(auth): gate new local signups
Mygod Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions config/local.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,13 @@
"areaRestrictions": [
{
"roles": [],
"areas": []
"areas": [],
"parent": []
},
{
"roles": [],
"areas": []
"areas": [],
"parent": []
}
],
"aliases": [
Expand Down
3 changes: 2 additions & 1 deletion packages/config/lib/mutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,10 @@ const applyMutations = (config) => {
})

config.authentication.areaRestrictions =
config.authentication.areaRestrictions.map(({ roles, areas }) => ({
config.authentication.areaRestrictions.map(({ roles, areas, parent }) => ({
roles: roles.flatMap(replaceAliases),
areas,
parent,
}))

config.authentication.strategies = config.authentication.strategies.map(
Expand Down
2 changes: 1 addition & 1 deletion packages/types/lib/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export type Config<Client extends boolean = false> = DeepMerge<
}
areas: ConfigAreas
authentication: {
areaRestrictions: { roles: string[]; areas: string[] }[]
areaRestrictions: { roles: string[]; areas: string[]; parent?: string[] }[]
// Unfortunately these types are not convenient for looping the `perms` object...
// excludeFromTutorial: (keyof BaseConfig['authentication']['perms'])[]
// alwaysEnabledPerms: (keyof BaseConfig['authentication']['perms'])[]
Expand Down
68 changes: 31 additions & 37 deletions server/src/graphql/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const { getPolyVector } = require('../utils/getPolyVector')
const { getPlacementCells } = require('../utils/getPlacementCells')
const { getTypeCells } = require('../utils/getTypeCells')
const { getValidCoords } = require('../utils/getValidCoords')
const { hasUnrestrictedAreaGrant } = require('../utils/areaPerms')
const {
getAccessibleScanAreasMenu,
} = require('../utils/getAccessibleScanAreasMenu')

/** @type {import("@apollo/server").ApolloServerOptions<import("@rm/types").GqlContext>['resolvers']} */
const resolvers = {
Expand Down Expand Up @@ -355,54 +359,44 @@ const resolvers = {
scanAreas: (_, _args, { req, perms }) => {
if (perms?.scanAreas) {
const scanAreas = config.getAreas(req, 'scanAreas')
const unrestrictedAreaGrant = hasUnrestrictedAreaGrant(
perms.areaRestrictions,
)
const hasDirectAreaAccess = (properties) =>
unrestrictedAreaGrant ||
!perms.areaRestrictions.length ||
perms.areaRestrictions.includes(properties.key)
const accessibleSelectableParents = new Set(
scanAreas.features
.filter(
(feature) =>
feature.properties.parent &&
!feature.properties.hidden &&
!feature.properties.manual &&
hasDirectAreaAccess(feature.properties),
)
.map((feature) => feature.properties.parent),
)
const canAccessArea = (properties) =>
hasDirectAreaAccess(properties) ||
(!properties.parent &&
!properties.manual &&
accessibleSelectableParents.has(properties.name))

return [
{
...scanAreas,
features: scanAreas.features.filter(
(feature) =>
!feature.properties.hidden &&
(!perms.areaRestrictions.length ||
perms.areaRestrictions.includes(feature.properties.name) ||
perms.areaRestrictions.includes(feature.properties.parent)),
!feature.properties.hidden && canAccessArea(feature.properties),
),
},
]
}
return [{ features: [] }]
},
scanAreasMenu: (_, _args, { req, perms }) => {
if (perms?.scanAreas) {
const scanAreas = config.getAreas(req, 'scanAreasMenu')
if (perms.areaRestrictions.length) {
const filtered = scanAreas
.map((parent) => ({
...parent,
children: perms.areaRestrictions.includes(parent.name)
? parent.children
: parent.children.filter((child) =>
perms.areaRestrictions.includes(child.properties.name),
),
}))
.filter((parent) => parent.children.length)

// // Adds new blanks to account for area restrictions trimming some
// filtered.forEach(({ children }) => {
// if (children.length % 2 === 1) {
// children.push({
// type: 'Feature',
// properties: {
// name: '',
// manual: !!config.getSafe('manualAreas.length'),
// },
// })
// }
// })
return filtered
}
return scanAreas.filter((parent) => parent.children.length)
}
return []
},
scanAreasMenu: (_, _args, { req, perms }) =>
getAccessibleScanAreasMenu(req, perms),
scannerConfig: (_, { mode }, { perms }) => {
const scanner = config.getSafe('scanner')
const modeConfig = scanner[mode]
Expand Down
12 changes: 11 additions & 1 deletion server/src/middleware/apollo.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { parse } = require('graphql')
const { state } = require('../services/state')
const { version } = require('../../../package.json')
const { DataLimitCheck } = require('../services/DataLimitCheck')
const { normalizeAreaRestrictions } = require('../utils/areaPerms')

/**
*
Expand All @@ -16,7 +17,16 @@ const { DataLimitCheck } = require('../services/DataLimitCheck')
function apolloMiddleware(server) {
return expressMiddleware(server, {
context: async ({ req, res }) => {
const perms = req.user ? req.user.perms : req.session.perms
const rawPerms = req.user ? req.user.perms : req.session.perms
const perms = rawPerms
? {
...rawPerms,
areaRestrictions: normalizeAreaRestrictions(
rawPerms.areaRestrictions || [],
req,
),
}
: rawPerms
const username = req?.user?.username || ''
const id = req?.user?.id || 0

Expand Down
47 changes: 26 additions & 21 deletions server/src/models/Weather.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const config = require('@rm/config')

const { getPolyVector } = require('../utils/getPolyVector')
const { getPolygonBbox } = require('../utils/getBbox')
const { consolidateAreas } = require('../utils/consolidateAreas')
const { hasUnrestrictedAreaGrant } = require('../utils/areaPerms')

class Weather extends Model {
static get tableName() {
Expand Down Expand Up @@ -41,15 +43,19 @@ class Weather extends Model {
/** @type {import("@rm/types").FullWeather[]} */
const results = await query

const areas = config.getSafe('areas')
const cleanUserAreas = (args.filters.onlyAreas || []).filter((area) =>
areas.names.has(area),
const unrestrictedAreaGrant = hasUnrestrictedAreaGrant(
perms.areaRestrictions,
)
const merged = perms.areaRestrictions.length
? perms.areaRestrictions.filter(
(area) => !cleanUserAreas.length || cleanUserAreas.includes(area),
)
: cleanUserAreas
const hasAreaFilter =
(!unrestrictedAreaGrant && perms.areaRestrictions.length) ||
(args.filters.onlyAreas || []).length
const merged = hasAreaFilter
? [...consolidateAreas(perms.areaRestrictions, args.filters.onlyAreas)]
: []

if (hasAreaFilter && !merged.length) {
return []
}

const boundPolygon = getPolygonBbox(args)
return results
Expand All @@ -61,21 +67,20 @@ class Weather extends Model {
(pointInPolygon(center, boundPolygon) ||
booleanOverlap(geojson, boundPolygon) ||
booleanContains(geojson, boundPolygon)) &&
(!merged.length ||
(!hasAreaFilter ||
merged.some(
(area) =>
areas.scanAreasObj[area] &&
(pointInPolygon(center, areas.scanAreasObj[area]) ||
booleanOverlap(geojson, areas.scanAreasObj[area]) ||
pointInPolygon(
point(
// @ts-ignore // again, probably need real TS types
areas.scanAreasObj[area].geometry.type === 'MultiPolygon'
? areas.scanAreasObj[area].geometry.coordinates[0][0][0]
: areas.scanAreasObj[area].geometry.coordinates[0][0],
),
geojson,
)),
pointInPolygon(center, area) ||
booleanOverlap(geojson, area) ||
pointInPolygon(
point(
// @ts-ignore // again, probably need real TS types
area.geometry.type === 'MultiPolygon'
? area.geometry.coordinates[0][0][0]
: area.geometry.coordinates[0][0],
),
geojson,
),
))
return (
hasOverlap && {
Expand Down
2 changes: 1 addition & 1 deletion server/src/routes/rootRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ rootRouter.get('/api/settings', async (req, res, next) => {
...Object.fromEntries(
Object.keys(authentication.perms).map((p) => [p, false]),
),
areaRestrictions: areaPerms(['none']),
areaRestrictions: areaPerms(['none'], req, true),
webhooks: [],
scanner: Object.keys(scanner).filter(
(key) =>
Expand Down
Loading
Loading