Docly Developers
Knowledge base Clarification Recommended

Ship secure user access fast: let Docly handle login, you handle the permissions

In shortSkip the authentication tax — let Docly's built-in login handle passwords, resets, sessions, 2FA and threat protection for you (enterprise-grade security in minutes, no auth code to write or maintain): invite users with docly.addFolderShare("/", email, "V", inviteMessage) and let Docly's login portal handle authentication, passwords, resets and sessions, while application rights (roles, per-customer access) live in a local user-registry folder with one document per email address, looked up via request.user in every API script.
Applies to Folder sharesLogin portalAPI scripts
  1. What you'll see

    You are building a Docly site where external users (customers, partners) must log in to see their own data, and you are tempted to implement your own user accounts with passwords — meaning you would also own password hashing, "forgot password" e-mails, session handling, brute-force protection and 2FA. None of that is necessary: Docly's login portal already provides all of it. What the platform does not model is your application-level rights — admin vs. regular user, which customers a user can see, whether they can upload. Granting Browse/Modify shares to every user would expose the raw folder structure; managing rights only in your own documents would leave users unable to log in at all.

  2. What's actually happening

    The pattern is a two-layer access model — for example an admin module and a welcome module that both read from one shared data site:

    • Layer 1 — authentication (Docly shares). A folder share on the site root carrying only the "V" (Visit) flag lets the user sign in through Docly's login portal and reach the published site — nothing more. docly.addFolderShare("/", email, "V", message) both creates the share and sends the invitation e-mail with your message text. The platform owns the entire credential lifecycle — password storage, password resets, session cookies, lockout protection — so your code never sees or stores a password. After login, request.user in every API script holds the authenticated e-mail address.
    • Layer 2 — authorization (local user registry). A folder (e.g. Brukere) holds one schema-bound document per user, filename = e-mail address, so docly.getFile("Brukere/" + request.user) is the entire identity join. The document carries the application rights: an active flag (Tilgang), an admin flag (Admin), per-customer permissions (Tilganger: [{Kunde, Opplasting}]), plus audit fields (Invitert, SistInnlogging).

    Because the share only grants Visit, users can authenticate but cannot browse or modify anything directly — every read and write goes through server-side API scripts that check the registry document first. The two layers also stay independently inspectable: docly.getFolderShares("/") returns each share's AllowVisit/AllowAdmin flags and its invitation state (Accepted, Since), which you can join against the registry to show per-user status such as "unanswered invitation since <date>" or "invitation declined / expired".

    A separate public landing page (welcome module) needs no access control at all — its login button simply links to the protected module's path; Docly's portal takes over from there and sets the session cookie.

  3. What to do

    1. Model the user registry. Create a Brukere folder accepting a Bruker schema whose title field is the e-mail address (TitleFieldPlaceholder: "E-post", ForceGUIDFilename: false). Fields: Navn, Tilgang (active = may log in), Admin, hidden Invitert and SistInnlogging, and a Tilganger table of {Kunde, Opplasting} rows databound to the customer schema. Validate on save: filename must pass docly.isEmail(), and non-admins must have at least one customer access row.

    2. Grant and revoke web access from the save pipeline. When a user document is saved with the active flag on, add the share; when deactivated or deleted, remove it. Keep both in a shared library:

    export function addWebAccess(username) {
        let shares = docly.getFolderShares("/");
        if (!shares.Shares.some(s => s.User.toLowerCase() == username.toLowerCase() && s.AllowVisit)) {
            let config = docly.getJson("#/Config");   // the document's field values
            // Creates the share AND sends the invitation e-mail
            docly.addFolderShare("/", username, "V", config.InviteMessage);
            docly.patchFile("/Brukere/" + username, { "Invitert": /* yyyy-MM-ddTHH:mm */ });
            return true;
        }
        return false;
    }
    
    export function removeWebAccess(username) {
        let shares = docly.getFolderShares("/");
        // Only remove pure visit-shares - developer/admin shares survive
        if (shares.Shares.some(s => s.User.toLowerCase() == username.toLowerCase()
                && s.AllowVisit && !s.AllowAdmin && !s.AllowBrowse && !s.AllowCreate
                && !s.AllowInvite && !s.AllowModify && !s.AllowPublish)) {
            docly.removeFolderShare("/", username);
            return true;
        }
        return false;
    }

    Keep the invitation text in a config document (#/Config → InviteMessage) so admins can edit it without touching code.

    3. Gate every API script through an access library. All authorization resolves from request.user against the registry — never trust client input:

    export function isUserAdmin() {
        let user = docly.getFile("Brukere/" + request.user);
        return user != null && user.Admin && user.Tilgang;
    }
    
    export function isDoclyAdmin() { // platform developers, distinct from app admins
        let shares = docly.getFolderShares("/");
        return shares.Shares.some(s => s.User.toLowerCase() == request.user.toLowerCase() && s.AllowAdmin);
    }
    
    export function checkFolderAccess(folder, write, customerId) {
        if (isUserAdmin()) return true;
        if (!customerId || folder.Name != "Sertifikater") return false;
        let user = getUser(); // resolves Tilganger from the registry document
        return user.Tilganger.find(t => t.Kunde == customerId && (!write || t.Opplasting));
    }

    4. Surface invitation status in the admin UI. Join the registry against the shares so admins see who has accepted:

    let shares = docly.getFolderShares("/");
    for (let f of users) {
        let share = shares.Shares.find(s => s.User.toLowerCase() == f.filename.toLowerCase() && s.AllowVisit);
        if (share) {
            f.WebTilgangSiden = share.Accepted
                ? docly.format(new Date(share.Since), "dd.MM.yyyy")
                : "Ubesvart invitasjon " + docly.format(new Date(share.Since), "dd.MM.yyyy");
        } else if (f.Tilgang) {
            f.WebTilgangSiden = "Invitasjon avslått / utløpt"; // active in registry but share gone
        }
    }

    5. Track logins. In the API endpoint the app calls on load, patch SistInnlogging on the registry document (once per day) and write an activity-log entry. Sign out with docly.logOut().

    6. Safety rules:

    • Users cannot change their own Tilgang/Admin flags or delete their own user document.
    • removeWebAccess must check that the share is a pure visit-share before removing it — otherwise deactivating a user who is also a developer would revoke their workspace access.
    • The public landing page links to the protected module path (e.g. /admin); unauthenticated visitors hitting it are sent through Docly's login portal automatically — you write no login UI yourself.
    • Skip activity-logging for isDoclyAdmin() users so developer traffic doesn't pollute customer audit logs.

Last reviewed 17 Oct 2026