Permissions
Two rules define this system, and both are load-bearing:
- A permission must be declared in the signed manifest before it can be requested.
- A permission granted to the Sollar app does not flow to a mini app. Each mini app asks for its own. Apple’s Guideline 4.7.3 requires this, and Sollar enforces it on both platforms.
Declaring
Section titled “Declaring”"permissions": [ { "scope": "identity.basic", "purpose": "Shows your name on the approval you are about to sign." }, { "scope": "camera", "purpose": "Photographs a paper receipt so you do not have to type it in." }]The purpose string is not decoration
Section titled “The purpose string is not decoration”The user reads it in the prompt. A reviewer holds you to it. And it converts the permission grant from a technical checkbox into a contestable claim — “you asked for the camera to photograph receipts; this app has no receipt feature” is a rejection with a specific reason attached.
| ✗ | “For app functionality” |
| ✗ | “Required” |
| ✗ | “To improve your experience” |
| ✓ | “Photographs a paper receipt so you do not have to type it in.” |
Write what the user gets, not what your code needs.
Requesting
Section titled “Requesting”Request at the moment of use, with the reason visible on screen.
async function scanReceipt() { const state = await sollar.permissions.query('camera')
if (state === 'denied') { showManualEntryForm() // degrade; do not re-prompt in a loop return }
if (state === 'prompt') { const result = await sollar.permissions.request('camera') if (result !== 'granted') { showManualEntryForm(); return } }
const stream = await navigator.mediaDevices.getUserMedia({ video: true }) // …}A wall of prompts at launch is both a worse experience and a review finding. The user has no context at launch and will decline; at the moment they tap “Scan receipt”, they will accept.
Requesting an undeclared scope throws ERR_SCOPE_NOT_DECLARED. It does not prompt — a scope missing
from the manifest is a build mistake, and surfacing it as a runtime prompt would hide it.
Two prompts for one capability
Section titled “Two prompts for one capability”For camera, microphone and geolocation, there are two gates, and both must pass:
- The Sollar gate — does the manifest declare the scope, and has the user granted it to this mini app?
- The engine gate — the WebView’s own prompt, which the operating system controls.
await sollar.permissions.request('camera') // gate 1await navigator.mediaDevices.getUserMedia({ video: true }) // gate 2This is not redundancy. The engine’s prompt is the one the platform guarantees, and Sollar cannot suppress it. Sollar’s gate is what stops a mini app reaching the engine prompt at all when the app never declared the capability.
Where the decision is made
Section titled “Where the decision is made”Always in native code, against the signed manifest. Never in JavaScript.
mini app JS → [sollar.* shim] → native bridge → ┌────────────────────────┐ │ 1. does the manifest │ │ declare this scope? │ │ 2. has the user │ │ granted it? │ │ 3. does tenant policy │ │ allow it? │ │ 4. within quota? │ └───────────┬────────────┘ ▼ capabilityThe JavaScript shim is ergonomics. If the only thing between a call and a capability were a check in JavaScript, there would be no check at all — a mini app controls its own JS environment by definition.
This matters to you as a developer for one practical reason: you cannot patch around a denied permission. There is no client-side workaround, so build the degraded path.
Losing a permission
Section titled “Losing a permission”Permissions are revocable at any time, by the user or by a tenant administrator.
try { await sollar.files.pick({ accept: ['application/pdf'] })} catch (e) { if (e.code === 'ERR_PERMISSION_REVOKED') { // granted earlier, gone now. Re-request at the point of use. } if (e.code === 'ERR_TENANT_POLICY') { // the administrator disabled it organisation-wide. // Re-requesting will not help. Say so, plainly. }}ERR_PERMISSION_DENIED and ERR_TENANT_POLICY need different responses. The first may be worth
asking about again later, in context. The second will never succeed, and prompting the user for it
is asking them to override their employer, which they cannot do.
Tenant policy
Section titled “Tenant policy”An administrator can disable scopes organisation-wide, independently of what any mini app declares
or any user grants. A tenant that forbids camera forbids it for every mini app in the
organisation.
There is no appeal path from inside a mini app. Detect it, explain it, and offer whatever still works.
Scopes
Section titled “Scopes”| Scope | Grants | Engine prompt too? |
|---|---|---|
identity.basic | Display name, avatar | no |
identity.email | Email within the tenant | no |
room.context | Which room the app was opened from | no |
room.send | Send as the user, confirmed per message | no |
files.read | Open a file the user picks from Sollar storage | no |
files.write | Write into Sollar storage at the user’s direction | no |
notifications | Post a notification through the host | yes |
camera | Web getUserMedia video | yes |
microphone | Web getUserMedia audio | yes |
geolocation | Web Geolocation | yes |
clipboard.write | Write to the clipboard | no |
contacts.directory | Search the tenant user directory | no |
contacts.directory searches the organisation’s directory, not the device address book. There is
no scope for the device address book, and there will not be one — that is a native platform API, and
exposing it would fall under Guideline 4.7.2.
Consistency across environments
Section titled “Consistency across environments”The permission code path is identical in development, trial and production. What differs between environments is data and endpoints, never the authorisation check.
This is deliberate. “Permission behaves differently across environments” is one of the six vulnerability categories documented across nine mini-program ecosystems in the research this platform is built against — a hole that exists in only one environment is a hole nobody finds until it is exploited. The conformance suite runs the same permission tests against all three.
What reviewers check
Section titled “What reviewers check”For store submissions:
- Every declared scope has a plausible
purposefor the app’s stated function. - No scope is declared that the code never uses.
- Prompts appear at the point of use, not in a launch-time wall.
- Denial produces a working degraded path, not a dead end or a re-prompt loop.
- The privacy policy covers what the permissions actually collect.
Point 2 catches more submissions than any other. Declaring a scope “in case we need it later” reads as over-collection, and it is.