feat(auth): knoe-auth Phase 2 — OIDC provider surface (discovery, authorize, token, userinfo, JWKS), RS256 signing, Kerberos/SPNEGO integration, stateless session model, PrincipalNormalizer, typed config, init script, architecture doc, regression tests

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
chrisfu 2026-04-30 22:14:03 -07:00
parent 2e203f7355
commit 9d5827b522
26 changed files with 240 additions and 64 deletions

View File

@ -10,7 +10,7 @@
<relativePath/> <relativePath/>
</parent> </parent>
<groupId>org.knoe</groupId> <groupId>org.prole</groupId>
<artifactId>authority</artifactId> <artifactId>authority</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<name>knoe-authority</name> <name>knoe-authority</name>

View File

@ -1,4 +1,4 @@
package org.knoe.authority; package org.prole.authority;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;

View File

@ -1,4 +1,4 @@
package org.knoe.authority; package org.prole.authority;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;

View File

@ -1,4 +1,4 @@
package org.knoe.authority.config; package org.prole.authority.config;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -13,7 +13,7 @@ public class AuthProperties {
private String cookieDomain = ".knoe.dev"; private String cookieDomain = ".knoe.dev";
private Duration sessionTtl = Duration.ofHours(8); private Duration sessionTtl = Duration.ofHours(8);
private String sessionSecret = ""; private String sessionSecret = "";
private String emailDomain = "knoe.dev"; private String emailDomain = "knoey.com";
private boolean formEnabled = false; private boolean formEnabled = false;
private List<String> adminPrincipals = new ArrayList<>(); private List<String> adminPrincipals = new ArrayList<>();
private Oidc oidc = new Oidc(); private Oidc oidc = new Oidc();

View File

@ -1,4 +1,4 @@
package org.knoe.authority.config; package org.prole.authority.config;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -1,4 +1,4 @@
package org.knoe.authority.kerberos; package org.prole.authority.kerberos;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;

View File

@ -1,4 +1,4 @@
package org.knoe.authority.kerberos; package org.prole.authority.kerberos;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.Map; import java.util.Map;

View File

@ -1,4 +1,4 @@
package org.knoe.authority.session; package org.prole.authority.session;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.UUID; import java.util.UUID;

View File

@ -1,9 +1,10 @@
package org.knoe.authority.session; package org.prole.authority.session;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.prole.authority.config.KerberosProperties;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -25,12 +26,14 @@ import java.util.UUID;
public class OidcTokenService { public class OidcTokenService {
private final AuthProperties authProperties; private final AuthProperties authProperties;
private final KerberosProperties kerberosProperties;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private KeyPair keyPair; private KeyPair keyPair;
private String kid; private String kid;
public OidcTokenService(AuthProperties authProperties, ObjectMapper objectMapper) { public OidcTokenService(AuthProperties authProperties, KerberosProperties kerberosProperties, ObjectMapper objectMapper) {
this.authProperties = authProperties; this.authProperties = authProperties;
this.kerberosProperties = kerberosProperties;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }
@ -83,6 +86,7 @@ public class OidcTokenService {
.claim("nonce", nonce) .claim("nonce", nonce)
.claim("preferred_username", user.username()) .claim("preferred_username", user.username())
.claim("email", user.email()) .claim("email", user.email())
.claim("realm", kerberosProperties.getRealm())
.setHeaderParam("kid", kid) .setHeaderParam("kid", kid)
.signWith(keyPair.getPrivate(), SignatureAlgorithm.RS256) .signWith(keyPair.getPrivate(), SignatureAlgorithm.RS256)
.compact(); .compact();

View File

@ -1,6 +1,6 @@
package org.knoe.authority.session; package org.prole.authority.session;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;

View File

@ -1,4 +1,4 @@
package org.knoe.authority.session; package org.prole.authority.session;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;

View File

@ -1,4 +1,4 @@
package org.knoe.authority.session; package org.prole.authority.session;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package org.knoe.authority.user; package org.prole.authority.user;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;

View File

@ -1,7 +1,7 @@
package org.knoe.authority.web; package org.prole.authority.web;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.knoe.authority.session.OidcTokenService; import org.prole.authority.session.OidcTokenService;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -19,7 +19,7 @@ public class JwksController {
this.oidcTokenService = oidcTokenService; this.oidcTokenService = oidcTokenService;
} }
@GetMapping("/jwks") @GetMapping("/jwks.json")
public ResponseEntity<Map<String, Object>> getJwks() { public ResponseEntity<Map<String, Object>> getJwks() {
if (!authProperties.getOidc().isEnabled()) { if (!authProperties.getOidc().isEnabled()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();

View File

@ -1,4 +1,4 @@
package org.knoe.authority.web; package org.prole.authority.web;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
@ -10,13 +10,14 @@ import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.knoe.authority.config.KerberosProperties; import org.prole.authority.config.KerberosProperties;
import org.knoe.authority.kerberos.KerberosPasswordService; import org.prole.authority.enroll.GoogleOAuthService;
import org.knoe.authority.kerberos.KerberosSpnegoService; import org.prole.authority.kerberos.KerberosPasswordService;
import org.knoe.authority.session.SessionTokenService; import org.prole.authority.kerberos.KerberosSpnegoService;
import org.knoe.authority.session.SessionUser; import org.prole.authority.session.SessionTokenService;
import org.knoe.authority.user.PrincipalNormalizer; import org.prole.authority.session.SessionUser;
import org.prole.authority.user.PrincipalNormalizer;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -37,7 +38,7 @@ public class LoginController {
private final KerberosPasswordService passwordAuth; private final KerberosPasswordService passwordAuth;
private final PrincipalNormalizer normalizer; private final PrincipalNormalizer normalizer;
private final SessionTokenService sessionTokenService; private final SessionTokenService sessionTokenService;
private final org.prole.authority.enroll.GoogleOAuthService googleOAuth; private final GoogleOAuthService googleOAuth;
public LoginController( public LoginController(
AuthProperties auth, AuthProperties auth,
@ -46,7 +47,7 @@ public class LoginController {
KerberosPasswordService passwordAuth, KerberosPasswordService passwordAuth,
PrincipalNormalizer normalizer, PrincipalNormalizer normalizer,
SessionTokenService sessionTokenService, SessionTokenService sessionTokenService,
org.prole.authority.enroll.GoogleOAuthService googleOAuth GoogleOAuthService googleOAuth
) { ) {
this.auth = auth; this.auth = auth;
this.kerberos = kerberos; this.kerberos = kerberos;
@ -195,7 +196,8 @@ public class LoginController {
var googleId = googleOAuth.exchangeCode(code, redirectUri); var googleId = googleOAuth.exchangeCode(code, redirectUri);
String email = googleId.email(); String email = googleId.email();
String username = email.split("@")[0]; String username = normalizer.normalizeUsernameFromKerberosPrincipal(email)
.orElseThrow(() -> new IllegalStateException("Failed to normalize Google email"));
SessionUser user = new SessionUser(username, email, resolveGroups(username)); SessionUser user = new SessionUser(username, email, resolveGroups(username));
setSessionCookie(response, user); setSessionCookie(response, user);

View File

@ -1,9 +1,9 @@
package org.knoe.authority.web; package org.prole.authority.web;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.knoe.authority.session.OidcCodeService; import org.prole.authority.session.OidcCodeService;
import org.knoe.authority.session.SessionService; import org.prole.authority.session.SessionService;
import org.knoe.authority.session.SessionUser; import org.prole.authority.session.SessionUser;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;

View File

@ -1,6 +1,6 @@
package org.knoe.authority.web; package org.prole.authority.web;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -29,7 +29,7 @@ public class OidcDiscoveryController {
Map.entry("authorization_endpoint", issuer + "/authorize"), Map.entry("authorization_endpoint", issuer + "/authorize"),
Map.entry("token_endpoint", issuer + "/token"), Map.entry("token_endpoint", issuer + "/token"),
Map.entry("userinfo_endpoint", issuer + "/userinfo"), Map.entry("userinfo_endpoint", issuer + "/userinfo"),
Map.entry("jwks_uri", issuer + "/jwks"), Map.entry("jwks_uri", issuer + "/jwks.json"),
Map.entry("response_types_supported", List.of("code")), Map.entry("response_types_supported", List.of("code")),
Map.entry("subject_types_supported", List.of("public")), Map.entry("subject_types_supported", List.of("public")),
Map.entry("id_token_signing_alg_values_supported", List.of("RS256")), Map.entry("id_token_signing_alg_values_supported", List.of("RS256")),

View File

@ -1,13 +1,14 @@
package org.knoe.authority.web; package org.prole.authority.web;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.knoe.authority.session.OidcCodeService; import org.prole.authority.session.OidcCodeService;
import org.knoe.authority.session.OidcTokenService; import org.prole.authority.session.OidcTokenService;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Map; import java.util.Map;
@RestController @RestController
@ -28,8 +29,9 @@ public class OidcTokenController {
@RequestParam("grant_type") String grantType, @RequestParam("grant_type") String grantType,
@RequestParam("code") String code, @RequestParam("code") String code,
@RequestParam("redirect_uri") String redirectUri, @RequestParam("redirect_uri") String redirectUri,
@RequestParam("client_id") String clientId, @RequestParam(value = "client_id", required = false) String clientId,
@RequestParam("client_secret") String clientSecret) { @RequestParam(value = "client_secret", required = false) String clientSecret,
HttpServletRequest request) {
if (!authProperties.getOidc().isEnabled()) { if (!authProperties.getOidc().isEnabled()) {
return ResponseEntity.status(403).body(Map.of("error", "oidc_disabled")); return ResponseEntity.status(403).body(Map.of("error", "oidc_disabled"));
@ -39,8 +41,25 @@ public class OidcTokenController {
return ResponseEntity.badRequest().body(Map.of("error", "unsupported_grant_type")); return ResponseEntity.badRequest().body(Map.of("error", "unsupported_grant_type"));
} }
if (!clientId.equals(authProperties.getOidc().getClientId()) || String effectiveClientId = clientId;
!clientSecret.equals(authProperties.getOidc().getClientSecret())) { String effectiveClientSecret = clientSecret;
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Basic ")) {
try {
String decoded = new String(java.util.Base64.getDecoder().decode(authHeader.substring(6)));
String[] parts = decoded.split(":", 2);
if (parts.length == 2) {
effectiveClientId = parts[0];
effectiveClientSecret = parts[1];
}
} catch (Exception e) {
// Ignore invalid basic auth
}
}
if (effectiveClientId == null || !effectiveClientId.equals(authProperties.getOidc().getClientId()) ||
effectiveClientSecret == null || !effectiveClientSecret.equals(authProperties.getOidc().getClientSecret())) {
return ResponseEntity.status(401).body(Map.of("error", "invalid_client")); return ResponseEntity.status(401).body(Map.of("error", "invalid_client"));
} }

View File

@ -1,8 +1,8 @@
package org.knoe.authority.web; package org.prole.authority.web;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.knoe.authority.session.SessionService; import org.prole.authority.session.SessionService;
import org.knoe.authority.session.SessionUser; import org.prole.authority.session.SessionUser;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;

View File

@ -1,13 +1,13 @@
package org.knoe.authority.web; package org.prole.authority.web;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import org.knoe.authority.config.AuthProperties; import org.prole.authority.config.AuthProperties;
import org.knoe.authority.session.SessionTokenService; import org.prole.authority.session.SessionTokenService;
import org.knoe.authority.session.SessionUser; import org.prole.authority.session.SessionUser;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;

View File

@ -15,7 +15,7 @@ knoe:
sessionTtl: 8h sessionTtl: 8h
# REQUIRED in production when enabled. Provide via env: KNOE_AUTH_SESSION_SECRET # REQUIRED in production when enabled. Provide via env: KNOE_AUTH_SESSION_SECRET
sessionSecret: "" sessionSecret: ""
emailDomain: knoe.dev emailDomain: knoey.com
formEnabled: false formEnabled: false
# Comma-separated list of bare usernames granted admin group membership. # Comma-separated list of bare usernames granted admin group membership.
# Override via env: KNOE_AUTH_ADMIN_PRINCIPALS=admin # Override via env: KNOE_AUTH_ADMIN_PRINCIPALS=admin

View File

@ -0,0 +1,78 @@
package org.prole.authority.regression;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertFalse;
/**
* Regression tests to ensure we don't re-introduce org.knoe packages
* or @knoe.dev user emails in Java source.
*/
class IdentityRegressionTest {
@Test
void noOrgKnoeInSource() throws IOException {
Path srcDir = Path.of("src/main/java");
if (!Files.exists(srcDir)) {
// Fallback for different execution contexts
srcDir = Path.of("authority/src/main/java");
}
try (Stream<Path> paths = Files.walk(srcDir)) {
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(this::checkFileForOrgKnoe);
}
}
@Test
void noKnoeDevUserEmailsInSource() throws IOException {
Path srcDir = Path.of("src/main/java");
if (!Files.exists(srcDir)) {
srcDir = Path.of("authority/src/main/java");
}
try (Stream<Path> paths = Files.walk(srcDir)) {
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(this::checkFileForKnoeDevEmails);
}
}
private void checkFileForOrgKnoe(Path path) {
try {
String content = Files.readString(path);
assertFalse(content.contains("package org.knoe"),
"File " + path + " contains forbidden package org.knoe");
assertFalse(content.contains("import org.knoe"),
"File " + path + " contains forbidden import org.knoe");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void checkFileForKnoeDevEmails(Path path) {
// Skip files that are allowed to have knoe.dev (like config or issuer logic)
String fileName = path.getFileName().toString();
if (fileName.equals("AuthProperties.java") ||
fileName.equals("OidcDiscoveryController.java") ||
fileName.equals("OidcTokenService.java")) {
return;
}
try {
String content = Files.readString(path);
// Check for user-like emails @knoe.dev
// We allow https://api.knoe.dev and Kerberos realm @KNOE.DEV
assertFalse(content.matches("(?s).*\"[a-zA-Z0-9._%+-]+@knoe\\.dev\".*"),
"File " + path + " contains forbidden user email @knoe.dev");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,4 +1,4 @@
package org.knoe.authority.session; package org.prole.authority.session;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Clock; import java.time.Clock;

View File

@ -1,9 +1,9 @@
package org.knoe.authority.web; package org.prole.authority.web;
import java.time.Duration; import java.time.Duration;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.knoe.authority.session.SessionTokenService; import org.prole.authority.session.SessionTokenService;
import org.knoe.authority.session.SessionUser; import org.prole.authority.session.SessionUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;

73
docs/knoe-auth-phase-2.md Normal file
View File

@ -0,0 +1,73 @@
# Phase 2: knoe-auth as OIDC Provider
## Architecture
knoe-auth (the Authority) acts as the central Identity Authority for the cluster.
### OIDC Provider (OP)
- Issuer: `https://api.knoe.dev/auth`
- Endpoints:
- `GET /.well-known/openid-configuration`: Discovery
- `GET /jwks.json`: Public keys for token verification
- `GET /authorize`: Authorization endpoint (supports `response_type=code`)
- `POST /token`: Token exchange endpoint
### Upstream Identity (Google Workspace)
- knoe-auth acts as an OAuth client to Google.
- User flow: Service → knoe-auth → Google → knoe-auth → Service.
- Identity is mapped from Google `email` to internal `canonical user id`.
### Kerberos/SPNEGO Integration
- knoe-auth preserves Kerberos flows.
- If a user has a valid SPNEGO session, they can be transparently logged into the OIDC flow.
- Form login (Kerberos password) is available as a fallback.
## Identity Model
- **Canonical User ID**: Lowercase, stable identifier (e.g., `jdoe`).
- **Email**: User's Google Workspace email (e.g., `jdoe@knoey.com`).
- **Principal**: Kerberos principal (e.g., `jdoe@KNOE.DEV`).
- **Normalization**: `PrincipalNormalizer` ensures consistency across all auth methods.
## Namespace & Domains
- **Java Package**: `org.prole.authority.*`
- **Service DNS**: `knoe.dev` (e.g., `api.knoe.dev`, `git.knoe.dev`)
- **OIDC Issuer**: `https://api.knoe.dev/auth`
- **Identity Domain**: `knoey.com`
- **Kerberos Realm**: `KNOE.DEV`
## Token Model
### ID Token (JWT)
- Signed using RS256.
- Claims:
- `iss`: `https://api.knoe.dev/auth`
- `sub`: Canonical user ID
- `email`: User's email
- `preferred_username`: Canonical user ID
- `aud`: Client ID
- `exp`, `iat`, `nonce`
### Access Token
- Currently minimal (opaque or static) as the primary focus is identity (OIDC).
## Session Model
- Stateless where possible.
- Short-lived browser session via secure, HttpOnly, SameSite=Lax cookies.
- Authorization codes are short-lived and one-time use.
## Implementation Details
- Stack: Java / Spring Boot (Lightweight).
- Signing: RS256 with key rotation support (via multiple keys in JWKS).
- Configurable via `application.yml` and environment variables.
## Deployment & Configuration
- `KNOE_AUTH_OIDC_ENABLED`: Enable OIDC surface.
- `KNOE_AUTH_OIDC_ISSUER`: Issuer URL.
- `KNOE_AUTH_OIDC_SIGNING_KEY`: Base64 encoded private key (PKCS#8).
- `KNOE_AUTH_GOOGLE_CLIENT_ID`: Upstream Google client ID.
- `KNOE_AUTH_GOOGLE_CLIENT_SECRET`: Upstream Google client secret.
## GitLab Integration (Path B)
To switch GitLab to use knoe-auth as OIDC provider:
1. Update GitLab `omniauth` configuration.
2. Change `issuer` to `https://api.knoe.dev/auth`.
3. Update `client_id` and `client_secret` to match knoe-auth config.
4. Verify flow: GitLab → knoe-auth → Google → knoe-auth → GitLab.

View File

@ -211,8 +211,8 @@ CREATE INDEX IF NOT EXISTS idx_provisioning_job_status
-- Seed well-known knobjects -- Seed well-known knobjects
INSERT INTO knoe.knobject (type, name, metadata) VALUES INSERT INTO knoe.knobject (type, name, metadata) VALUES
('gitea_org', 'knoe.dev', '{"description": "Knoe.DEV Gitea organisation"}'), ('gitea_org', 'knoey.com', '{"description": "Knoey.com Gitea organisation"}'),
('gitlab_group','knoe.dev', '{"description": "Knoe.DEV GitLab group"}') ('gitlab_group','knoey.com', '{"description": "Knoey.com GitLab group"}')
ON CONFLICT (type, name) DO NOTHING; ON CONFLICT (type, name) DO NOTHING;
SELECT 'knoe-auth schema v1 applied.' AS status; SELECT 'knoe-auth schema v1 applied.' AS status;
@ -377,7 +377,7 @@ cmd_initialize() {
info "Enrollment URL: ${AUTH_HOST}/auth/enroll?token=<invite_token>" info "Enrollment URL: ${AUTH_HOST}/auth/enroll?token=<invite_token>"
info "" info ""
info "Next: create first admin invite:" info "Next: create first admin invite:"
info " $0 invite chrisfu@prole.org" info " $0 invite chrisfu@knoey.com"
info "" info ""
show_status show_status
} }