This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. # User QR Encoding This document formally defines a QR Code format that encodes the minimum identifiable user information for use by other services. ## Use Cases - Recommended, i.e. good, use cases: - Verifying usage of non-critical machinery per user - Tally-counting access/usage of certain things - Non-critical user-personalized actions, such as printing out news for a user on a receipt printer - NOT recommended, i.e. possible but please don't: - Door Badging (NOT recommended, see [Security](#security)) - Access-gating critical/dangerous machinery ## Requirements - Encodes at minimum the user ID and username - Robust at allowing users to change username - Allow insecure/non-critical systems to obtain embedded information _without_ computation/verification if needed for low-power/embedded purposes - Allow secure verification using remote server to obtain additional, more secretive information about current user (e.g., list of groups or avatar URL) - QR Code must be small (has at most 4 eyes in most cases, rather than 9 eyes or more) ## Designs ### QR Code The QR Code encodes a string encoded in Alphanumeric mode. The string must implement this syntax: ``` QR_STRING := 'HTTPS://DMA.SPACE/QR/' CLAIMS_PART '.' SIGNATURE_PART ``` > **Note:** All characters MUST be upper-cased for Alphanumeric mode. ### Base32 Encoding The QR string makes extensive use of standard Base32 encoding with NO padding, as defined in [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648). ### Claims Part ``` CLAIMS_PART := USER_ID ':' USERNAME_BASE32 ':' USER_ROLE ':' ISSUED_DATE USER_ID := /[0-9]+/ USER_ROLE := 'ADMIN' | 'MEMBER' | '_' ISSUED_DATE := YEAR '-' MONTH '-' DATE ``` The claims part is described in the syntax above, with the following notes: - `UNAME_BASE32` means the username is encoded using the predefined Base32 encoding. - If the user's role has neither admin nor member, then `_` must be used to denote either normal user or unknown role. As an example, this claims string: ``` 10:MRUWC3LPNZSA:ADMIN:2026-01-01 ``` Means the following: - The user's ID is `10` - The username is `diamond` (decoded from the Base32 string `MRUWC3LPNZSA`) - The user's role is `admin`, meaning they belong in the `dma-admins` IdP group - The claims dates back to January 1st, 2026 ### Signature Type and Part ``` SIGNATURE_PART := SIGNATURE_ED25519 | ... SIGNATURE_ED25519 := 'ED25519:' BASE32_DATA ``` The signature type denotes the type of the signature string that follows after it and the colon (`:`) character. The signature part is the signature of the entire [Claims Part](#claims-part) string, signed using whatever the signature type denotes, and encoded using the predefined Base32 encoding. The supported signature types are: - `ED25519` An example signature part of the above claims part string example could be: ``` ED25519:7CSS7U7C2BJM3Z3MXYENYNSBUWZRS3BGT4YWX4DXTMDBOWUABFBT4REZSKJ4FCVTFXCFY6A2WNOUIMIR3HHGLQT5CNA5ZABNOBPBMBY ``` ### Implementation and Example The following function implements the algorithm in Go using a fake random source to generate the Ed25519 key (INSECURE) [aasadasds][asdasdasd]: ```go func encodeEd25519(priv ed25519.PrivateKey, uid int, uname string, urole string, issuedAt time.Time) []byte { enc := base32.StdEncoding.WithPadding(base32.NoPadding) plx := fmt.Sprintf( "%d:%s:%s:%s", uid, enc.EncodeToString([]byte(uname)), urole, issuedAt.Format("2006-01-02"), ) sig := ed25519.Sign(priv, []byte(plx)) return fmt.Appendf(nil, "HTTPS://DMA.SPACE/QR/%s.ED25519:%s", plx, enc.EncodeToString(sig)) } ``` The example outputs above was generated with: ``` issuedAt := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) constRand := rand.NewChaCha8([32]byte{}) _, priv, _ := ed25519.GenerateKey(constRand) encodeEd25519(priv, 10, "diamond", roleAdmin, issuedAt) ``` This returns the following string: ``` HTTPS://DMA.SPACE/QR/10:MRUWC3LPNZSA:ADMIN:2026-01-01.ED25519:7CSS7U7C2BJM3Z3MXYENYNSBUWZRS3BGT4YWX4DXTMDBOWUABFBT4REZSKJ4FCVTFXCFY6A2WNOUIMIR3HHGLQT5CNA5ZABNOBPBMBY ``` ### Frontend Server Design The frontend server must be able to handle `/QR` paths and perform a redirection to the Monolith API server's `GET /api/qr` endpoint. The query string must be kept as-is. ### Verification Server Design This section is not finalized. Proposed endpoints: - `/qr/*`: same as `/QR/*` - `/QR/keys.json`: returns the public keys that are used to sign - `/QR/<CODE>`: redirect to user-specific page or custom URL if we don't have this feature yet. This allows any user to scan this code as normal and be redirected to a nice webpage. - `/QR/<CODE>/verify`: verify the signature ONLY (any client can verify themselves using the public key). - `/QR/<CODE>/claims`: verify the signature and return the additional user claims. ```sh $ curl -X GET "HTTPS://DMA.SPACE/QR/10:MRUWC3LPNZSA:ADMIN:2026-01-01.ED25519:7CSS7U7C2BJM3Z3MXYENYNSBUWZRS3BGT4YWX4DXTMDBOWUABFBT4REZSKJ4FCVTFXCFY6A2WNOUIMIR3HHGLQT5CNA5ZABNOBPBMBY/verify" { "valid": true } $ curl -X GET "HTTPS://DMA.SPACE/QR/10:MRUWC3LPNZSA:ADMIN:2026-01-01.ED25519:7CSS7U7C2BJM3Z3MXYENYNSBUWZRS3BGT4YWX4DXTMDBOWUABFBT4REZSKJ4FCVTFXCFY6A2WNOUIMIR3HHGLQT5CNA5ZABNOBPBMBY/claims" { "valid": true, "claims": { "sub": 10, "username": "diamond", "preferred_name": "diamond", "email": "diamond@dma.space", "groups": ["dma-users", "dma-members", "dma-admins"] } } ``` The Monolith should implement a `GET /api/qr`. ## Security This design does prevent fake QR codes from being created, so imitating another person is not trivial. However, the QR code can still be copied or stolen from others, so the whole system is not perfect and should not be used to gate access to critical things.