{"record":{"id":"3a211217499ea416","repo":"theonedev/onedev","slug":"service-or-ai-account-not-allowed-to-login","errorCode":null,"errorMessage":"Service or AI account not allowed to login","messagePattern":"Service or AI account not allowed to login","errorType":"exception","errorClass":"DisabledAccountException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/security/DefaultAuthenticatingService.java","lineNumber":145,"sourceCode":"\t\t\temailAddressService.create(emailAddress);\n\t\t}\n\t\tsyncGroupsAndSshKeys(user, false, authenticated, defaultGroupName);\n\t}\n\t\n\t@Override\n\tprotected final AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) \n\t\t\tthrows AuthenticationException {\n\t\treturn transactionService.call(() -> {\n\t\t\ttry {\n\t\t\t\tvar userName = normalizeUserName((String) token.getPrincipal());\n\t\t\t\tvar user = userService.findByVerifiedEmailAddress((String) token.getPrincipal());\n\t\t\t\tif (user == null) \n\t\t\t\t\tuser = userService.findByName(userName);\n\t\t\t\tif (user != null) {\n\t\t\t\t\tif (user.isDisabled())\n\t\t\t\t\t\tthrow new DisabledAccountException(_T(\"Account is disabled\"));\n\t\t\t\t\telse if (user.getType() != ORDINARY)\n\t\t\t\t\t\tthrow new DisabledAccountException(_T(\"Service or AI account not allowed to login\"));\n\t\t\t\t\tif (user.getPassword() == null) {\n\t\t\t\t\t\tvar authenticator = settingService.getAuthenticator();\n\t\t\t\t\t\tif (authenticator != null) {\n\t\t\t\t\t\t\tvar authenticated = authenticator.authenticate((UsernamePasswordToken) token);\n\t\t\t\t\t\t\tvar emailAddressValue = authenticated.getEmail();\n\t\t\t\t\t\t\tif (emailAddressValue != null) {\n\t\t\t\t\t\t\t\tvar emailAddress = emailAddressService.findByValue(emailAddressValue);\n\t\t\t\t\t\t\t\tif (emailAddress != null) {\n\t\t\t\t\t\t\t\t\tif (emailAddress.getOwner().equals(user) || !emailAddress.isVerified()) {\n\t\t\t\t\t\t\t\t\t\tupdateUser(user, authenticated, emailAddress, authenticator.getDefaultGroup());\n\t\t\t\t\t\t\t\t\t\treturn user;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthrow new AuthenticationException(MessageFormat.format(_T(\"Email address \\\"{0}\\\" already used by another account\"), emailAddressValue));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tupdateUser(user, authenticated, null, authenticator.getDefaultGroup());\n\t\t\t\t\t\t\t\t\treturn user;\n\t\t\t\t\t\t\t\t}","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/security/DefaultAuthenticatingService.java#L127-L163","documentation":"During authentication, after resolving the user, OneDev checks the user's type. Only ORDINARY (human) users may log in via the username/password path; SERVICE and AI accounts are rejected with a DisabledAccountException. Service accounts are intended for internal use (e.g. system operations) and AI accounts for agent access, so interactive password login is blocked for them.","triggerScenarios":"A login attempt with the credentials (name/password) of a user whose getType() is SERVICE or AI instead of ORDINARY - typically via the sign-in form or HTTP basic auth.","commonSituations":"Teams create a 'bot' or 'ci' user and set its type to Service to mark it non-human, then try to sign in to the web UI with it; or a script uses the service account's password directly.","solutions":["Use an access token issued for the account instead of username/password where the API/git allows it.","If interactive login is genuinely needed, change the account type back to Ordinary in Administration -> Users.","Create a dedicated ordinary user for human-style access and grant it the needed permissions.","For AI/service integrations, use the integration mechanisms intended for those account types rather than password auth."],"exampleFix":"// before: service account password login\ncurl -u svc-bot:password https://onedev.example.com/api/projects\n// after: use an access token for the account\ncurl -H \"Authorization: Bearer <access-token>\" https://onedev.example.com/api/projects","handlingStrategy":"validation","validationCode":"if (user.getType() !== 'ORDINARY') {\n  throw new Error('Use access tokens or intended integration flows for service/AI accounts');\n}","typeGuard":"function isOrdinaryUser(u) { return u != null && u.type === 'ORDINARY'; }","tryCatchPattern":"try {\n  authenticate(name, password);\n} catch (DisabledAccountException e) {\n  if (e.getMessage().contains(\"Service or AI account\")) {\n    switchToTokenAuth();\n  }\n}","preventionTips":["Document that service/AI accounts authenticate via tokens, never passwords.","Keep bot accounts marked Service only when no interactive login is required.","Provision dedicated ordinary users when humans need to share access."],"tags":["authentication","user-management","account-type"],"backgroundTag":"account-disabled","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}