Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
7771fc0
fix(mail): wrap mail subtype data in sumeru root element
ATRIwOX Oct 2, 2026
47103a5
refactor(web): consolidate flash, API key, and rate-limit handlers
ATRIwOX Oct 2, 2026
24add05
feat(bus): SUM-PLAT-15 durable events, NOTIFY fan-out, and channel ACL
ATRIwOX Oct 2, 2026
0bb3ac0
feat(mail): SUM-PLAT-14 followers, notifications, inbox bell, and mai…
ATRIwOX Oct 2, 2026
cb08632
feat(auth): SUM-PLAT-13 OIDC PKCE, JWKS id_token verify, and login MF…
ATRIwOX Oct 2, 2026
5625a5e
feat(auth): SUM-PLAT-13 provider admin UI and self-service TOTP enrol…
ATRIwOX Oct 2, 2026
f110961
feat(web): SUM-PLAT-13 enterprise login and setup shell with SSO buttons
ATRIwOX Oct 2, 2026
b3eaff3
fix(login): attach password toggle to pre-wrapped login fields
ATRIwOX Oct 2, 2026
b09f232
docs: document SSO providers, TOTP enrollment, and bus/mail auth paths
ATRIwOX Oct 2, 2026
4564068
fix: dashboard side pannel for chatter and mail is visible
ATRIwOX Oct 2, 2026
3fe7ca1
Merge pull request #111 from ProjectMeru/platform
CHINMAYVIVEK Oct 2, 2026
0e0c9d8
refactor(render): centralize engine HTML template relative paths
ATRIwOX Oct 2, 2026
26dab0d
refactor(templates): organize engine HTML under shell, auth, pages, p…
ATRIwOX Oct 2, 2026
27fea0e
feat(web): login tenant branding with company logo and default Sumeru…
ATRIwOX Oct 2, 2026
4facf87
test(web): login branding resolver, logo route, and auth HTML shell
ATRIwOX Oct 2, 2026
060b9d3
Merge pull request #112 from ProjectMeru/platform
ATRIwOX Oct 2, 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
27 changes: 27 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,33 @@ System administrators see a **bug icon** in the web top bar (`features.debugMenu

With developer mode on, the top-bar **bug menu** shows sectioned actions (Record, User interface, Security, Tools). Each form field label gets an **info icon**; hover it for a technical popover (field, model, domain, modifiers). **Metadata** and **Data** open modals; **Access rights** opens the secondary debug drawer (collapsed by default). The **field inspector** toggle enables click-to-select on the field widget without blocking normal input when off. **SWC Vision** and **Open metrics** remain admin-gated.

## Realtime bus channels (SWC WebSocket)

Clients connect to `GET /web/swc/bus` (session cookie). Wire frames are JSON:

- Client → server: `subscribe` / `unsubscribe` (with `channels`, optional `last_event_id`), `ping`.
- Server → client: `event` (`id`, `channel`, `payload`), `pong`, `error`.

**Channel ACL** (server enforces before subscribe):

| Prefix | Rule |
|--------|------|
| `user/{uid}/…` | Session uid must match `{uid}` |
| `group/{xmlid}` | User must belong to group |
| `record/{model}/{id}` | Read access on record |
| `company/{id}` | User allowed company |
| `model/{model}` | Model read ACL |

Events are persisted in `sys.bus.event`; PostgreSQL `NOTIFY sumeru_bus` fan-out supports multiple app processes. Payloads carry record ids only — never field values from elevated writes.

## Mail thread and notifications

Models with `mail_thread` on the embedded model tag auto-subscribe creator and common assignee fields on create. Chatter uses `mail.message` subtypes (`mail.message.subtype`). Followers (`mail.follower`) drive `mail.notification` rows and optional HTML email via the mail queue. @mentions in chatter bodies notify mentioned users by login.

## Auth providers and MFA

Configure IdPs under **Settings → Security → Authentication providers** (`sys.auth.provider`; PKCE on start/callback). **Linked identities** lists `core.user.identity` rows. The login page shows enabled providers as SSO buttons. Local password login stays available unless system parameter `auth.local_enabled` is `false` and at least one provider is enabled. TOTP: users enroll under **Settings → Account security**; login uses `totp_enabled` / `totp_secret` with an HMAC-signed pending-MFA cookie before session creation; trusted devices use a signed cookie bound to the user id. SAML is not implemented — use OIDC-capable IdPs. When `jwks_url` is set, the callback verifies the `id_token` signature against JWKS (RS256/ES256) before linking the user.

## View modifier expressions (SWC)

Dynamic `invisible` / `readonly` / `required` expressions in form and list arch are evaluated client-side with a **frozen allowlist** of identifiers: record field names, `user_id`, `company_id`, and `context` (object). Expressions must be boolean JavaScript fragments (for example `state == 'done'`), not statements. Tokens such as `function`, `=>`, `[`, `` ` ``, or `;` are rejected. Static arch flags still apply when an expression is missing or invalid. List column expressions that reference record fields are evaluated without a row context (static arch flags apply). Action `context` on the workspace payload is not wired yet — `context` is an empty object until then.
Expand Down
4 changes: 4 additions & 0 deletions addons/base/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"views/sys_report_action_list_views.xml",
"views/sys_report_action_form_views.xml",
"views/security_actions.xml",
"views/auth_actions.xml",
"views/sys_auth_provider_list_views.xml",
"views/sys_auth_provider_form_views.xml",
"views/core_user_identity_list_views.xml",
"views/core_company_form_views.xml",
"views/core_company_kanban_views.xml",
"views/core_company_list_views.xml",
Expand Down
2 changes: 2 additions & 0 deletions addons/base/models/core_company.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ type CoreCompany struct {
MailChatterEnabled sdk.Boolean `sumeru:"string=Chatter,default=true"`
MailActivityPanelEnabled sdk.Boolean `sumeru:"string=Activity panel,default=true"`
Color sdk.Integer `sumeru:"string=Color Index"`
LoginLogo sdk.Text `sumeru:"string=Login logo"`
LoginTagline sdk.String `sumeru:"string=Login tagline"`
}
13 changes: 13 additions & 0 deletions addons/base/models/core_user_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package models

import (
"sumeru/core/sdk"
)

type CoreUserIdentity struct {
sdk.Model `sumeru:"model=core.user.identity"`

ProviderID sdk.Many2One[SysAuthProvider] `sumeru:"required,index,string=Provider"`
Subject sdk.String `sumeru:"required,index,string=Subject"`
UserID sdk.Many2One[CoreUser] `sumeru:"required,index,string=User"`
}
14 changes: 14 additions & 0 deletions addons/base/models/core_user_trusteddevice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package models

import (
"sumeru/core/sdk"
)

type CoreUserTrustedDevice struct {
sdk.Model `sumeru:"model=core.user.trusteddevice"`

UserID sdk.Many2One[CoreUser] `sumeru:"required,index,string=User"`
TokenHash sdk.String `sumeru:"required,index,string=Token Hash,column=token_hash"`
UserAgent sdk.String `sumeru:"string=User Agent,column=user_agent"`
ExpiresAt sdk.DateTime `sumeru:"required,index,string=Expires At,column=expires_at"`
}
22 changes: 22 additions & 0 deletions addons/base/models/sys_auth_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package models

import (
"sumeru/core/sdk"
)

type SysAuthProvider struct {
sdk.Model `sumeru:"model=sys.auth.provider"`

Name sdk.String `sumeru:"required,string=Name"`
ProviderType sdk.String `sumeru:"required,string=Type,default=oidc,selection=oidc:OpenID Connect,oauth2:OAuth2"`
ClientID sdk.String `sumeru:"required,string=Client ID"`
ClientSecret sdk.String `sumeru:"string=Client Secret"`
IssuerURL sdk.String `sumeru:"string=Issuer URL"`
AuthorizeURL sdk.String `sumeru:"string=Authorize URL"`
TokenURL sdk.String `sumeru:"string=Token URL"`
JwksURL sdk.String `sumeru:"string=JWKS URL"`
Scopes sdk.String `sumeru:"string=Scopes,default=openid email profile"`
Enabled sdk.Boolean `sumeru:"string=Enabled,default=false"`
ButtonLabel sdk.String `sumeru:"string=Button Label"`
LinkPolicy sdk.String `sumeru:"string=Unknown Users,default=deny,selection=deny:Deny,link:Link by email,jit_internal:JIT internal,jit_portal:JIT portal"`
}
3 changes: 3 additions & 0 deletions addons/base/models/zmodels.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions addons/base/views/auth_actions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<sumeru>
<data>
<action id="action_sys.auth.provider" type="window" model="sys.auth.provider" name="Authentication Providers" view_mode="list,form"/>
<action id="action_core.user.identity" type="window" model="core.user.identity" name="Linked Identities" view_mode="list"/>
</data>
</sumeru>
9 changes: 9 additions & 0 deletions addons/base/views/core_company_form_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
</group>
</group>

<group string="Login branding">
<group>
<field name="login_logo" string="Login logo" widget="image"/>
</group>
<group>
<field name="login_tagline" string="Login tagline" placeholder="Shown on the sign-in page…"/>
</group>
</group>

<group string="Address">
<group>
<field name="street" string="Street"/>
Expand Down
10 changes: 10 additions & 0 deletions addons/base/views/core_user_identity_list_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<sumeru>
<data>
<view id="view_core.user.identity_list" model="core.user.identity" type="list">
<field name="provider_id" string="Provider"/>
<field name="subject" string="Subject"/>
<field name="user_id" string="User"/>
</view>
</data>
</sumeru>
2 changes: 1 addition & 1 deletion addons/base/views/core_users_form_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<group>
<label for="login" string="Login is used to sign in. Keep it unique."/>
<field name="login" string="Login"/>
<field name="totp_enabled" string="Two-Factor Authentication"/>
<field name="totp_enabled" string="Two-Factor Authentication" readonly="true"/>
<field name="password_min_len" string="Min Password Length"/>
</group>
</page>
Expand Down
2 changes: 2 additions & 0 deletions addons/base/views/menus.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<menuitem id="menu_sys_field_access" name="Field Access" parent="menu_security_section" sequence="30" action="base.action_sys.field.access" groups="base.group_system" web_icon="settings"/>
<menuitem id="menu_sys_field_access_matrix" name="Field access matrix" parent="menu_security_section" sequence="40" action="base.action_sys.field.access_matrix" groups="base.group_system" web_icon="settings"/>
<menuitem id="menu_sys_access_matrix" name="Model access matrix" parent="menu_security_section" sequence="45" action="base.action_sys.access_matrix" groups="base.group_system" web_icon="settings"/>
<menuitem id="menu_sys_auth_provider" name="Authentication providers" parent="menu_security_section" sequence="50" action="base.action_sys.auth.provider" groups="base.group_system" web_icon="settings"/>
<menuitem id="menu_core_user_identity" name="Linked identities" parent="menu_security_section" sequence="55" action="base.action_core.user.identity" groups="base.group_system" web_icon="settings"/>

<!-- Platform -->
<menuitem id="menu_platform_section" name="Platform" parent="menu_settings_root" sequence="16" web_icon="settings" groups="base.group_system"/>
Expand Down
41 changes: 41 additions & 0 deletions addons/base/views/sys_auth_provider_form_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<sumeru>
<data>
<view id="view_sys.auth.provider_form" model="sys.auth.provider" type="form">
<sheet>
<div class="sum_title">
<h1>
<field name="name" placeholder="e.g. Corporate Azure AD"/>
</h1>
</div>
<group>
<group string="General">
<field name="enabled" string="Enabled"/>
<field name="button_label" string="Sign-in button label" placeholder="Sign in with Azure"/>
<field name="provider_type" string="Protocol"/>
<field name="link_policy" string="Unknown users"/>
</group>
<group string="OAuth client">
<field name="client_id" string="Client ID"/>
<field name="client_secret" string="Client secret"/>
<field name="scopes" string="Scopes"/>
</group>
</group>
<group string="Endpoints">
<group>
<field name="issuer_url" string="Issuer URL"/>
<field name="authorize_url" string="Authorize URL"/>
</group>
<group>
<field name="token_url" string="Token URL"/>
<field name="jwks_url" string="JWKS URL"/>
</group>
</group>
<group string="Login callback">
<label string="Redirect URI registered at your IdP must be: https://YOUR_HOST/web/auth/oauth/callback (use http only in local development)."/>
<label string="To hide password login, set System Parameter auth.local_enabled to false (requires at least one enabled provider)."/>
</group>
</sheet>
</view>
</data>
</sumeru>
12 changes: 12 additions & 0 deletions addons/base/views/sys_auth_provider_list_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<sumeru>
<data>
<view id="view_sys.auth.provider_list" model="sys.auth.provider" type="list">
<field name="name" string="Name"/>
<field name="provider_type" string="Type"/>
<field name="enabled" string="Enabled"/>
<field name="button_label" string="Button Label"/>
<field name="link_policy" string="Unknown Users"/>
</view>
</data>
</sumeru>
16 changes: 16 additions & 0 deletions addons/mail/data/mail_subtype_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<sumeru>
<data noupdate="true">
<record id="mail_subtype_comment" model="mail.message.subtype">
<field name="name">comment</field>
<field name="description">User discussion</field>
<field name="default">True</field>
</record>
<record id="mail_subtype_notification" model="mail.message.subtype">
<field name="name">notification</field>
<field name="description">System notification</field>
<field name="internal">True</field>
<field name="default">True</field>
</record>
</data>
</sumeru>
Loading
Loading