[data-reveal]{opacity:1!important;transform:none!important}
Migrate from 0.4 to 0.5
Building

Migrate from 0.4 to 0.5

What a module or client written for @antelopejs/interface-dms 0.4 checks or changes when the permission, settings and account-security changes land.

These changes tighten permission checks, open the settings root to every member and harden the account-security flows. Most projects only need the checklist; each section shows the code before and after.

Checklist

AreaWhat to doSection
PermissionsStore every ancestor id with a granted permission, in role fixtures and external resolversPermission ancestors
SettingsNothing for the settings root; set memberAccess: true on a page that relied on inheriting the root's accessSettings root
Account securityUpdate clients and end-to-end tests that drive 2FA, password reset, email change or sign-outAccount security
Notification countsReplace unseenCount / markAllSeen with unreadCount; drop calls to /unseen-count and /seenNotification counts
New optional fieldsNothing to doNew fields

A permission counts only with its ancestors

HasPermission now counts a permission a role holds only when every ancestor id is held too. An ancestor that is not a registered permission, or that every member holds (defaultGranted, such as settings or the memberAccess pages), counts as held. The wildcard *, module scopes and defaultGranted leaves work as before.

Roles saved through the DMS (the roles editor, /settings/workspace/roles/* and /api/tables/roles) are completed with their ancestor ids on save, so nothing changes for them. A role written another way, a test fixture, or an external resolver such as a plan definition, lists the whole chain:

// Before: the delete action alone let the action through
permissions: ["library.table-view.table-view-archive.table.delete"];

// After: the page, its table and their categories come with it
permissions: [
  "library",
  "library.table-view",
  "library.table-view.table-view-archive",
  "library.table-view.table-view-archive.table",
  "library.table-view.table-view-archive.table.delete",
];

See Auth & permissions.

The settings root opens to every member

settingsCategory is memberAccess: "self": /settings opens to every signed-in member and lists only the settings they can open, and the roles editor no longer offers a "Settings" node (the workspace settings show at its top level). "self" opens the overview alone: a page declared under settingsCategory still needs its own permission.

memberAccess: true passes on to what is declared under it; "self" does not. A page that relied on inheriting the settings root's access declares memberAccess: true itself, or sits in a category of its own flagged true (Personal settings):

// Before: opened to every member through the page it is filed under
export class MyPrefsPage extends PageController("my-prefs", { displayName: "My preferences", category: AccountPage }) {}

// After
export class MyPrefsPage extends PageController("my-prefs", {
  displayName: "My preferences",
  category: AccountPage,
  memberAccess: true,
}) {}

Account security

These flows changed for every user; update a client or an end-to-end test that drives them:

  • Email 2FA setup: POST /settings/user/security/two-factor/enable-email still takes { currentPassword } but only sends a code to the account email. POST /settings/user/security/two-factor/confirm-email { code } turns the method on.
  • 2FA step: POST /api/auth/verify-2fa accepts at most 5 codes per challenge, then answers 429 error.too_many_2fa_attempts: sign in with the password again. An account takes at most 10 wrong codes within an hour across its challenges, then every code answers 429 error.too_many_2fa_attempts_account until the window passes, even after a new sign-in. A challenge opens one session, then answers 401 error.2fa_challenge_used. The two-factor token carries a random jti, so two sign-ins get two challenges. An authenticator code is accepted once. Every bad two-factor token (not a JWT, unknown user, bad signature, expired, wrong purpose) answers the same 401 error.invalid_2fa_token on verify-2fa and request-2fa-email, instead of Invalid 2FA token, Invalid user or the token library's message (jwt expired, invalid signature), and a missing token answers 400 error.invalid_2fa_token.
  • Current password: the forms that ask for it again (email change, password change, adding a 2FA method, account deletion) share 10 tries per account within 15 minutes, then answer 429 error.too_many_current_password_attempts, the right password included; the right password clears the count.
  • Stored 2FA email code: User.twoFactorEmailCode holds a SHA-256 hash of [userId, code], no longer the code. A module that writes it must store the hash.
  • Password reset: the code is matched in any case, is withdrawn after 5 wrong tries, and a reset signs out every session.
  • Email change: the code is withdrawn after 5 wrong tries (429 error.email_change_too_many_attempts), and the one-code-a-minute limit survives a cancel and is checked before the current password: a second request within the minute answers 429 error.rate_limited even with a wrong password.
  • Sign-out: an access token stops working as soon as its session is signed out or deleted, instead of at its expiry.
  • Invitations: POST /api/auth/validate-invite-token { token, email } tells a revoked, replaced, expired or used link apart. Signing up through an invitation always marks the email verified, with no validation round even under auth.mustValidateEmail.
  • Removed skipEmailValidation: gone from UserInvite, InviteUserToTenantOptions and CreateUserInviteTokenOptions; an invite signup always validates the address. Stop passing it (TypeScript refuses it). The Members invite form and the invitation edit form no longer offer it.

The bell counts unread notifications

The header bell and the Notifications menu badge now show the same thing: the unread notifications, drawn in the tone of the most important one. The "unseen since the bell was last opened" count is gone:

  • useNotifications() no longer returns unseenCount, markAllSeen or handleRemoteSeen; read unreadCount and the new unreadTone.
  • GET /settings/user/notifications/unseen-count and PUT /settings/user/notifications/seen are removed, /counts no longer returns unseen (it returns unreadTone), and the notification:seen realtime event is no longer published.
  • A nav badge may now carry a tone: navBadge({ count }) can return { count, tone }, and a table tab takes navBadge: { tone }. Several badges on one page add up in the strongest tone. See Navigation badges.

New optional fields

Additive, nothing to migrate:

  • User.twoFactorTotpLastStep: the last accepted authenticator time step.
  • InviteResolution.inviteToken and InviteResolutionsModel.getForToken(token).
  • PageLayout.allComponentsHidden: set when a page declares blocks and the caller may see none.