gitoriaLog in with ident

gitoria

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitfd981932fd981932State of 2026-09-27; bin/ no longer tracked (Hybriel commit is in README)mrefd981932/components/main.hl

5.2 KB

  1. // components/main.hl — THE SHELL: the application header (brand; top right the login: ident's
  2. // "Log in with ident" button, or who you are + Log out), the display-name prompt of a first login,
  3. // and the slot every page renders into. All CSS is in styles.hl.
  4. // THE SELECTOR (gitoria#14): `<ident-selector key=IDENT_API_KEY>` from <ident>/selector.js, as in tickets.
  5. // Choosing an identity fires `ident-login` (one-time code); /login.js hands it to the hidden input
  6. // #identcode, whose `change` goes to the face gitoriaLogin (server-side exchange, no reload). The element's
  7. // class is `in` / `out`; /login.js mirrors it into the selector's `loggedIn`. ident only answers a REGISTERED
  8. // origin (the main address), so on a repo address (the request's host, `host = null`, hybriel#74) the selector
  9. // carries the class `onrepo` and styles.hl hides it — the button stays there.
  10. // LOGIN: a plain link to <ident>/login?key=&return=<public url>/login/callback (project.hl
  11. // loginCallback); /login.js adds `?next=<this page>` at the click so the login returns here.
  12. // FIRST LOGIN: no display name yet → the name prompt; creating a repo waits for it.
  13. import { siteName } from '../project.hl'
  14. import { identKey, selectorScript, loginHref, infoOfSession, exchangeCode, ensureUser, userInfo, setUserName, userOfSession, slugOfHost } from '../users.hl'
  15. import { randomBytes } from 'hl:crypto'
  16. slot = null
  17. session = null
  18. host = null
  19. me = infoOfSession(session)
  20. loggedIn = me != null
  21. loggedOut = me == null
  22. needsName = me != null && !me.named
  23. myName = me != null && me.named ? me.name : 'you'
  24. buttonHref = loginHref
  25. onRepoHost = slugOfHost(host) != ''
  26. selectorClass = (me != null ? 'in' : 'out') + (onRepoHost ? ' onrepo' : '')
  27. selectorKey = identKey
  28. identScript = selectorScript
  29. loginError = ''
  30. hasLoginError = false
  31. nameDraft = ''
  32. nameError = ''
  33. View {
  34. body {
  35. applicationHeader {
  36. a { class = "brand" href = "/" siteName }
  37. userBox { id = "userbox"
  38. identSelector { id = "selector" key = selectorKey class = selectorClass }
  39. if (loggedIn) {
  40. span { id = "whoami" myName }
  41. button { id = "logout" type = "button" class = "quiet" "Log out" on click(e) { emit doLogout(e) } }
  42. }
  43. if (loggedOut) {
  44. a { id = "loginbutton" class = "button" href = buttonHref "Log in with ident" }
  45. }
  46. input { id = "identcode" type = "hidden" on change(e) { emit gotCode(e) } }
  47. }
  48. }
  49. if (hasLoginError) { p { id = "loginerror" class = "message banner" loginError } }
  50. if (needsName) {
  51. namePrompt {
  52. form { id = "nameform"
  53. on submit(e) {
  54. e.preventDefault()
  55. emit saveName(e)
  56. }
  57. label { "Welcome! Choose the display name others see as the owner of your repositories"
  58. input { id = "displayname" name = "displayname" required = "required" maxlength = "60" value = nameDraft on input(e) { emit setNameDraft(e.target.value) } }
  59. }
  60. button { id = "namesave" type = "submit" "Save" }
  61. p { id = "nameerror" class = "message" nameError }
  62. }
  63. }
  64. }
  65. main { slot }
  66. script { src = identScript }
  67. script { src = "/login.js" }
  68. }
  69. }
  70. showMe = (info) => {
  71. loggedIn = true
  72. loggedOut = false
  73. needsName = !info.named
  74. myName = info.named ? info.name : 'you'
  75. selectorClass = onRepoHost ? 'in onrepo' : 'in'
  76. loginError = ''
  77. hasLoginError = false
  78. }
  79. showOut = () => {
  80. loggedIn = false
  81. loggedOut = true
  82. needsName = false
  83. myName = 'you'
  84. selectorClass = onRepoHost ? 'out onrepo' : 'out'
  85. }
  86. // the selector's code (via /login.js and the hidden input) → the server exchanges it
  87. on gotCode(e) {
  88. let code = e.target.value
  89. e.target.value = ''
  90. if (code == null || code == '') { return null }
  91. let r = emit server gitoriaLogin(code)
  92. if (r == null || r.error != null) {
  93. loginError = r != null ? r.error : 'the login failed'
  94. hasLoginError = true
  95. return null
  96. }
  97. showMe(r)
  98. }
  99. on client gitoriaSignedIn(tag, info) { showMe(info) }
  100. on client gitoriaSignedOut(tag) { showOut() }
  101. on setNameDraft(v) { nameDraft = v }
  102. on saveName(e) {
  103. let r = emit server gitoriaSaveName(nameDraft)
  104. if (r == null || r.error != null) {
  105. nameError = r != null ? r.error : 'could not save the name'
  106. return null
  107. }
  108. nameError = ''
  109. showMe(r)
  110. }
  111. on doLogout(e) {
  112. emit server gitoriaLogOut()
  113. showOut()
  114. }
  115. // ---- the faces (the trailing `session` is always the server's: hybriel #16) ----
  116. on server gitoriaLogin(code, session) {
  117. if (session == null) { return { error = 'no session — reload the page' } }
  118. let x = exchangeCode(code)
  119. if (x.error != null) { return { error = x.error } }
  120. let u = ensureUser(x.identity)
  121. if (u == null) { return { error = 'could not store the user' } }
  122. let tag = randomBytes(16)
  123. session.user = { id = u.id }
  124. session.data.tag = tag
  125. let info = userInfo(u)
  126. emit client gitoriaSignedIn(tag, info)
  127. return info
  128. }
  129. on server gitoriaSaveName(name, session) {
  130. let u = userOfSession(session)
  131. if (u == null) { return { error = 'log in with ident first' } }
  132. let r = setUserName(u.id, name)
  133. if (r.error != null) { return { error = r.error } }
  134. let info = userInfo(r.user)
  135. emit client gitoriaSignedIn(session.data.tag, info)
  136. return info
  137. }
  138. on server gitoriaLogOut(session) {
  139. if (session == null) { return { error = 'no session' } }
  140. emit client gitoriaSignedOut(session.data.tag)
  141. session.user = null
  142. return { ok = true }
  143. }

Branches

Latest commits

  • fd981932State of 2026-09-27; bin/ no longer tracked (Hybriel commit is in README)mre
  • 4a2d7125initial commitmre