Skip to content

Use party manager permissions in Users app - #376

Merged
dt2patel merged 2 commits into
mainfrom
codex/users-party-permissions
Jun 11, 2026
Merged

Use party manager permissions in Users app#376
dt2patel merged 2 commits into
mainfrom
codex/users-party-permissions

Conversation

@dt2patel

Copy link
Copy Markdown
Contributor

Business summary

Closes #375.

Visible SGC_USER permissions in the Users app should map to real Users app behavior instead of appearing as unused administrative noise. This PR wires existing party manager and party security assignment permissions into Users app access, profile/contact actions, and security group assignment while preserving the current SECURITY_* gates for security maintenance.

Changes

  • Broadens user list/detail access to USERS_LIST_VIEW OR PARTYMGR_VIEW OR PARTYMGR_ADMIN.
  • Allows user creation with party-manager create/admin or existing security create/admin permissions.
  • Adds app actions for party profile updates, party status updates, contact create/update/delete, and security group assignment.
  • Gates contact edit/remove, profile/status controls, and security group assignment controls with the new app actions.
  • Adds missing route meta permission checks for user list, create security group, and add permissions routes.

Validation

  • npm run lint passed with one existing warning in src/store/modules/permission/actions.ts: unused dispatch.
  • npm run build was run by the worker before my amend and failed on existing $router typing errors in unrelated views (CreateUser.vue, LocalLogin.vue, Permissions.vue, UserConfirmation.vue, UserQuickSetup.vue).
  • npm run test:unit is not available in this repo; Vue CLI reports command "test:unit" does not exist.

dt2patel added 2 commits June 10, 2026 17:43
Adds a local-only login page and auth path so the Users app can run against a local Moqui instance during development.

Keep this as a logically separate commit so it can be dropped before preparing the real app-change PR.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for local Moqui login, including a new LocalLogin view, updated routing guards, and Moqui-specific API integrations in UserService. It also refines permission controls across several components (UserDetails, ContactActionsPopover, and SecurityGroupActionsPopover) using newly defined permission actions and rules. Feedback on these changes highlights a potential invalid URL generation in normalizeMoquiBaseUrl when all URL sources are empty, an optimization opportunity to avoid an extra API call during permission pagination, and a bug in getExpirationTime where ISO date strings are not parsed correctly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +11 to +19
const normalizeMoquiBaseUrl = (instanceUrl?: string): string => {
const url = (instanceUrl || store.getters['user/getInstanceUrl'] || process.env.VUE_APP_LOCAL_MOQUI_URL || '').trim();
const baseUrl = url
.replace(/\/rest\/s1.*$/, '')
.replace(/\/api\/?$/, '')
.replace(/\/+$/, '');

return baseUrl.startsWith('http') ? `${baseUrl}/rest/s1/` : `https://${baseUrl}.hotwax.io/rest/s1/`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If instanceUrl, user/getInstanceUrl, and VUE_APP_LOCAL_MOQUI_URL are all empty or undefined, url will be an empty string. This causes baseUrl to be empty, and since it does not start with 'http', the function returns https://.hotwax.io/rest/s1/, which is an invalid URL.

We should handle the empty url case gracefully by returning an empty string.

Suggested change
const normalizeMoquiBaseUrl = (instanceUrl?: string): string => {
const url = (instanceUrl || store.getters['user/getInstanceUrl'] || process.env.VUE_APP_LOCAL_MOQUI_URL || '').trim();
const baseUrl = url
.replace(/\/rest\/s1.*$/, '')
.replace(/\/api\/?$/, '')
.replace(/\/+$/, '');
return baseUrl.startsWith('http') ? `${baseUrl}/rest/s1/` : `https://${baseUrl}.hotwax.io/rest/s1/`;
}
const normalizeMoquiBaseUrl = (instanceUrl?: string): string => {
const url = (instanceUrl || store.getters['user/getInstanceUrl'] || process.env.VUE_APP_LOCAL_MOQUI_URL || '').trim();
if (!url) return '';
const baseUrl = url
.replace(/\/rest\/s1.*$/, '')
.replace(/\/api\/?$/, '')
.replace(/\/+$/, '');
return baseUrl.startsWith('http') ? `${baseUrl}/rest/s1/` : `https://${baseUrl}.hotwax.io/rest/s1/`;
}

Comment on lines +113 to +118
if (resp.status === 200 && resp.data.docs?.length && !hasError(resp)) {
serverPermissions.push(...resp.data.docs.map((permission: any) => permission.permissionId));
viewIndex++;
} else {
resp = null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When fetching user permissions, if the returned page of permissions is not full (i.e., resp.data.docs.length < viewSize), we have already reached the end of the results. We can terminate the loop immediately instead of making an extra API call that will return an empty list. This reduces unnecessary network requests.

        if (resp.status === 200 && resp.data.docs?.length && !hasError(resp)) {
          serverPermissions.push(...resp.data.docs.map((permission: any) => permission.permissionId));
          if (resp.data.docs.length < viewSize) {
            resp = null;
          } else {
            viewIndex++;
          }
        } else {
          resp = null;
        }

Comment thread src/views/LocalLogin.vue
Comment on lines +90 to +95
const getExpirationTime = (expirationTime?: string | number) => {
const parsedExpiration = Number(expirationTime);
return Number.isFinite(parsedExpiration) && parsedExpiration > Date.now()
? parsedExpiration
: Date.now() + ONE_DAY_IN_MS;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using Number(expirationTime) will return NaN if expirationTime is an ISO date string (which is a common format for expiration times). We should use Date.parse() to support parsing both ISO date strings and numeric timestamps correctly.

const getExpirationTime = (expirationTime?: string | number) => {
  if (!expirationTime) return Date.now() + ONE_DAY_IN_MS;
  const parsedExpiration = typeof expirationTime === 'number' ? expirationTime : Date.parse(expirationTime);
  return Number.isFinite(parsedExpiration) && parsedExpiration > Date.now()
    ? parsedExpiration
    : Date.now() + ONE_DAY_IN_MS;
}

@dt2patel
dt2patel marked this pull request as ready for review June 11, 2026 04:40
@dt2patel
dt2patel merged commit 29305ae into main Jun 11, 2026
2 checks passed
@dt2patel
dt2patel deleted the codex/users-party-permissions branch June 11, 2026 04:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use party manager permissions in Users app access and profile actions

1 participant