Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,11 +382,11 @@ mutation {
mutation {
createTenant(input: {
name: "factory-a",
route: "factory-a"
alias: "factory-a"
}) {
id
name
route
alias
status
}
}
Expand All @@ -395,12 +395,14 @@ mutation {
createEntity(input: {
profileId: "client-profile-id",
name: "meter-001",
alias: "meter-001",
attributes: {
serial_no: "WM-001"
}
}) {
id
kind
alias
profileId
profileVersionId
attributes
Expand All @@ -411,13 +413,15 @@ mutation {
createResource(input: {
kind: "channel",
name: "telemetry",
alias: "telemetry",
attributes: {
topic: "telemetry"
}
}) {
id
kind
name
alias
attributes
}
}
Expand Down Expand Up @@ -792,7 +796,7 @@ active | inactive | frozen | deleted
| ---------------- | -------------------- |
| domain `id` | `tenants.id` |
| domain `name` | `tenants.name` |
| `route` | `tenants.route` |
| domain `route` | `tenants.alias` |
| `metadata` | `tenants.attributes` |
| `tags` | `tenants.tags` |
| `enabled` | `status = active` |
Expand Down
52 changes: 52 additions & 0 deletions apidocs/grpc-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
- [CheckRequest](#atom-v1-CheckRequest)
- [CheckRequest.ContextEntry](#atom-v1-CheckRequest-ContextEntry)
- [CheckResponse](#atom-v1-CheckResponse)
- [ResolveAliasRequest](#atom-v1-ResolveAliasRequest)
- [ResolveAliasResponse](#atom-v1-ResolveAliasResponse)
- [ResolveCertificateRequest](#atom-v1-ResolveCertificateRequest)
- [ResolveCertificateResponse](#atom-v1-ResolveCertificateResponse)
- [RevokeEntityCertificatesRequest](#atom-v1-RevokeEntityCertificatesRequest)
- [RevokeEntityCertificatesResponse](#atom-v1-RevokeEntityCertificatesResponse)

- [AliasService](#atom-v1-AliasService)
- [AuthService](#atom-v1-AuthService)
- [AuthzService](#atom-v1-AuthzService)
- [CertificateService](#atom-v1-CertificateService)
Expand Down Expand Up @@ -113,6 +116,41 @@



<a name="atom-v1-ResolveAliasRequest"></a>

### ResolveAliasRequest



| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| tenant_id | [string](#string) | | Tenant selector — exactly one of tenant_id, tenant_alias, or global must be set. tenant_alias is the case-folded tenant slug. |
| tenant_alias | [string](#string) | | |
| object_kind | [string](#string) | | Which table the object alias addresses: &#34;entity&#34; (clients/devices) or &#34;resource&#34; (channels). Other values are rejected. Generic on purpose — no domain/channel vocabulary. |
| object_alias | [string](#string) | | The object&#39;s alias slug, unique within the tenant. |
| global | [bool](#bool) | | Resolve an entity or resource whose tenant_id is NULL. |






<a name="atom-v1-ResolveAliasResponse"></a>

### ResolveAliasResponse



| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| tenant_id | [string](#string) | | empty string for global objects |
| object_id | [string](#string) | | |






<a name="atom-v1-ResolveCertificateRequest"></a>

### ResolveCertificateRequest
Expand Down Expand Up @@ -184,6 +222,20 @@



<a name="atom-v1-AliasService"></a>

### AliasService
AliasService resolves human-friendly alias slugs to canonical UUIDs.
Atom owns the alias registry and its uniqueness; callers (e.g. a message
broker) resolve an alias once, cache the UUID, then authorize by UUID via
AuthzService.Check. Resolution is capability-neutral — it reveals only the
UUIDs; the Check call is the authorization gate.

| Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------|
| ResolveAlias | [ResolveAliasRequest](#atom-v1-ResolveAliasRequest) | [ResolveAliasResponse](#atom-v1-ResolveAliasResponse) | |


<a name="atom-v1-AuthService"></a>

### AuthService
Expand Down
56 changes: 54 additions & 2 deletions apidocs/grpc.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# gRPC API Reference

Atom exposes three gRPC services on port **8081** by default, configurable with `GRPC_ADDR`.
Atom exposes four gRPC services on port **8081** by default, configurable with `GRPC_ADDR`.

The proto source lives at [`proto/atom/v1/atom.proto`](../proto/atom/v1/atom.proto). The generated proto reference lives at [`apidocs/grpc-reference.md`](./grpc-reference.md) and should be regenerated only when the proto changes.

Expand All @@ -18,7 +18,7 @@ Runtime services should call Atom over the service network. The default containe

### Authentication Metadata

`AuthzService.Check`, `CertificateService.ResolveCertificate`, and `CertificateService.RevokeEntityCertificates` require gRPC metadata:
`AuthzService.Check`, `AliasService.ResolveAlias`, `CertificateService.ResolveCertificate`, and `CertificateService.RevokeEntityCertificates` require gRPC metadata:

```text
authorization: Bearer <jwt-or-api-key>
Expand Down Expand Up @@ -149,6 +149,58 @@ grpcurl -plaintext \

---

### `atom.v1.AliasService`

Alias resolution converts human-friendly tenant/entity/resource handles into canonical UUIDs. Resolution does not grant access; callers must authorize the returned object UUID separately with `AuthzService.Check`.

#### `ResolveAlias`

```text
rpc ResolveAlias(ResolveAliasRequest) returns (ResolveAliasResponse)
```

Requires `authorization: Bearer <token>` metadata.

Exactly one tenant selector is required:

- `tenant_id` for a tenant UUID;
- `tenant_alias` for a case-insensitive tenant alias;
- `global = true` for an entity or resource whose `tenant_id` is null.

`object_kind` must be exactly `entity` or `resource` (case-insensitive). Other values return `INVALID_ARGUMENT`.

**Request: `ResolveAliasRequest`**

| Field | Type | Required | Description |
|---|---|---|---|
| `tenant_id` | `string` UUID | conditional | Tenant UUID selector. |
| `tenant_alias` | `string` | conditional | Tenant alias selector. |
| `global` | `bool` | conditional | Select the global null-tenant namespace. |
| `object_kind` | `string` | yes | `entity` or `resource`. |
| `object_alias` | `string` | yes | Object alias within the selected namespace. |

**Response: `ResolveAliasResponse`**

| Field | Type | Description |
|---|---|---|
| `tenant_id` | `string` UUID | Resolved tenant; empty for global objects. |
| `object_id` | `string` UUID | Resolved entity or resource UUID. |

**Example**

```bash
grpcurl -plaintext \
-H 'authorization: Bearer '"$ATOM_TOKEN" \
-d '{
"tenant_alias": "factory-a",
"object_kind": "resource",
"object_alias": "telemetry"
}' \
atom:8081 atom.v1.AliasService/ResolveAlias
```

---

### `atom.v1.CertificateService`

Certificate runtime lookup and entity-wide certificate revocation for services that terminate mTLS outside Atom.
Expand Down
28 changes: 23 additions & 5 deletions apidocs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ components:
$ref: '#/components/schemas/EntityKind'
name:
type: string
alias:
type: string
nullable: true
tenant_id:
type: string
format: uuid
Expand Down Expand Up @@ -252,6 +255,8 @@ components:
name:
type: string
example: sensor-01
alias:
type: string
tenant_id:
type: string
format: uuid
Expand All @@ -263,6 +268,9 @@ components:
properties:
name:
type: string
alias:
type: string
nullable: true
status:
$ref: '#/components/schemas/EntityStatus'
attributes:
Expand Down Expand Up @@ -597,6 +605,9 @@ components:
name:
type: string
nullable: true
alias:
type: string
nullable: true
tenant_id:
type: string
format: uuid
Expand Down Expand Up @@ -625,6 +636,8 @@ components:
example: channel
name:
type: string
alias:
type: string
tenant_id:
type: string
format: uuid
Expand All @@ -639,6 +652,9 @@ components:
properties:
name:
type: string
alias:
type: string
nullable: true
attributes:
type: object

Expand Down Expand Up @@ -899,7 +915,7 @@ components:
format: uuid
name:
type: string
route:
alias:
type: string
nullable: true
status:
Expand Down Expand Up @@ -933,8 +949,9 @@ components:
name:
type: string
example: factory-1
route:
alias:
type: string
nullable: true
tags:
type: array
items:
Expand All @@ -947,8 +964,9 @@ components:
properties:
name:
type: string
route:
alias:
type: string
nullable: true
tags:
type: array
items:
Expand Down Expand Up @@ -2384,7 +2402,7 @@ paths:
in: query
schema:
type: string
- name: route
- name: alias
in: query
schema:
type: string
Expand Down Expand Up @@ -2425,7 +2443,7 @@ paths:
$ref: '#/components/schemas/CreateTenant'
example:
name: factory-1
route: factory-1
alias: factory-1
tags: [pilot]
attributes:
magistrala:
Expand Down
4 changes: 2 additions & 2 deletions app/components/app-shell/tenant-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ const GLOBAL_OPTION: TenantSelection = { id: GLOBAL_TENANT, name: "Global" };
const TENANTS_QUERY = `
query TenantSwitcher {
tenants(limit: 100, offset: 0) {
items { id name route }
items { id name alias }
}
}
`;

type TenantsData = {
tenants: { items: { id: string; name: string; route: string | null }[] };
tenants: { items: { id: string; name: string; alias: string | null }[] };
};

export function TenantSwitcher() {
Expand Down
34 changes: 34 additions & 0 deletions app/components/crud/table/initial-values.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import {
entityFormInitialValues,
resourceFormInitialValues,
} from "@/components/crud/table/initial-values";

describe("alias form initial values", () => {
it("loads entity aliases for editing", () => {
const values = entityFormInitialValues({
id: "entity-1",
name: "Sensor",
alias: "sensor-01",
kind: "device",
});

expect(values.alias).toBe("sensor-01");
});

it("loads resource aliases for editing", () => {
const values = resourceFormInitialValues({
id: "resource-1",
kind: "resource:channel",
name: "Telemetry",
alias: "telemetry",
});

expect(values.alias).toBe("telemetry");
});

it("uses an empty alias when the row has none", () => {
expect(entityFormInitialValues({ id: "entity-1" }).alias).toBe("");
expect(resourceFormInitialValues({ id: "resource-1" }).alias).toBe("");
});
});
4 changes: 3 additions & 1 deletion app/components/crud/table/initial-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function resourceFormInitialValues(row: Row): ResourceFormInitialValues {
id: String(row.id),
kind: typeof row.kind === "string" ? row.kind : "",
name: typeof row.name === "string" ? row.name : "",
alias: typeof row.alias === "string" ? row.alias : "",
tenantId: typeof row.tenantId === "string" ? row.tenantId : "",
ownerId: typeof row.ownerId === "string" ? row.ownerId : "",
attributes:
Expand Down Expand Up @@ -87,6 +88,7 @@ export function entityFormInitialValues(row: Row): EntityFormInitialValues {
return {
id: String(row.id),
name: typeof row.name === "string" ? row.name : "",
alias: typeof row.alias === "string" ? row.alias : "",
kind: (ENTITY_KINDS as readonly string[]).includes(rawKind)
? (rawKind as EntityFormInitialValues["kind"])
: "human",
Expand All @@ -105,7 +107,7 @@ export function tenantFormInitialValues(row: Row): TenantFormInitialValues {
return {
id: String(row.id),
name: typeof row.name === "string" ? row.name : "",
route: typeof row.route === "string" ? row.route : "",
alias: typeof row.alias === "string" ? row.alias : "",
tags: Array.isArray(row.tags) ? row.tags.map((tag) => String(tag)) : [],
attributes:
row.attributes && typeof row.attributes === "object"
Expand Down
Loading
Loading