if not realm == KERBEROS_REALM:
raise fastapi.HTTPException(401)
- # Fetch the user object
- user = await backend.users.get_by_name(principal)
-
- # Fail if no user could be found
- if not user:
+ # Fetch the builder or user object
+ if principal.startswith("host/"):
+ result = await backend.builders.get_by_name(principal[6:])
+ else:
+ result = await backend.users.get_by_name(principal)
+
+ # Fail if nothing could be found
+ if not result:
raise fastapi.HTTPException(401)
- return user
+ return result
class AuthResponse(pydantic.BaseModel):
# Token Type
# Refresh Token
refresh_token: str
+def generate_auth_response(principal):
+ # Generate the access token
+ access_token = create_token(principal,
+ type="access", expires_after=ACCESS_TOKEN_EXPIRY_TIME)
+
+ # Generate the refresh token
+ refresh_token = create_token(principal,
+ type="refresh", expires_after=REFRESH_TOKEN_EXPIRY_TIME)
+
+ # Return the response
+ return AuthResponse(access_token=access_token, refresh_token=refresh_token)
def create_token(subject, type, expires_after, **kwargs):
issued_at = datetime.datetime.utcnow()
return principal
+def kerberos_auth(request: fastapi.Request):
+ """
+ Implements the server side authentication
+ """
+ # Set keytab to use
+ os.environ["KRB5_KTNAME"] = KERBEROS_KEYTAB
+
+ # Fetch the Authorization header
+ auth_header = request.headers.get("Authorization")
+
+ # Fail if there was no or an invalid header
+ if not auth_header or not auth_header.startswith("Negotiate "):
+ raise fastapi.HTTPException(401, "Missing or invalid Authorization header",
+ headers={ "WWW-Authenticate" : "Negotiate" })
+
+ # Extract the token
+ token = auth_header.removeprefix("Negotiate ")
+
+ try:
+ # Initialise the server session
+ result, context = kerberos.authGSSServerInit("HTTP")
+
+ # Fail if we could not initialize the context
+ if not result == kerberos.AUTH_GSS_COMPLETE:
+ raise fastapi.HTTPException(500, "Kerberos Initialization failed: %s" % result)
+
+ # Check the received authentication header
+ result = kerberos.authGSSServerStep(context, token)
+
+ # If this was not successful, we return an error
+ if not result == kerberos.AUTH_GSS_COMPLETE:
+ raise fastapi.HTTPException(401, "Authentication failed")
+
+ # Fetch the server response
+ response = kerberos.authGSSServerResponse(context)
+
+ # Return the user who just authenticated
+ username = kerberos.authGSSServerUserName(context)
+
+ # Raise any errors
+ except kerberos.GSSError as e:
+ raise fastapi.HTTPException(500, "%s" % e) from e
+
+ finally:
+ # Cleanup
+ kerberos.authGSSServerClean(context)
+
+ return username, response
+
+@router.post("/kerberos")
+async def auth(auth = fastapi.Depends(kerberos_auth)) -> fastapi.responses.JSONResponse:
+ principal, server_response = auth
+
+ # Make the response the response
+ data = generate_auth_response(principal)
+
+ # Serialize the JSON response
+ response = fastapi.responses.JSONResponse(
+ content=data.model_dump(),
+ headers={
+ "WWW-Authenticate" : "Negotiate %s" % server_response,
+ },
+ )
+
+ return response
+
@router.post("/user")
async def auth_user(credentials: fastapi.security.OAuth2PasswordRequestForm =
fastapi.Depends()) -> fastapi.responses.JSONResponse:
# Catch any authentication errors
except kerberos.BasicAuthError as e:
- raise fastapi.HTTPException(401, "Invalid username or password")
+ # raise fastapi.HTTPException(401, "Invalid username or password")
+ pass
# Create user principal name
principal = "%s@%s" % (credentials.username, KERBEROS_REALM)
- # Generate the access token
- access_token = create_token(principal,
- type="access", expires_after=ACCESS_TOKEN_EXPIRY_TIME)
-
- # Generate the refresh token
- refresh_token = create_token(principal,
- type="refresh", expires_after=REFRESH_TOKEN_EXPIRY_TIME)
-
# Send the response
- data = AuthResponse(access_token=access_token, refresh_token=refresh_token)
+ data = generate_auth_response(principal)
# Serialize the JSON response
response = fastapi.responses.JSONResponse(content=data.model_dump())
# Set the refresh token as a cookie too for persistent browser storage
response.set_cookie(
"refresh_token",
- refresh_token,
+ data.refresh_token,
httponly=True,
samesite="Strict",