mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
* feat(auth): introduce new timeout constant * feat(auth): pass timeout from handler * feat(auth): add timeout selector to auth settings view * feat(settings): add user session timeout property * feat(auth): load user session timeout from settings * fix(settings): use correct time format * feat(auth): remove no-auth flag * refactor(auth): move timeout mgmt to jwt service * refactor(client): remove no-auth checks from client * refactor(cli): remove defaultNoAuth * feat(settings): create settings with default user timeout value * refactor(db): save user session timeout always * refactor(jwt): return error * feat(auth): set session timeout in jwt service on update * feat(auth): add description and time settings * feat(auth): parse duration * feat(settings): validate user timeout format * refactor(settings): remove unneccesary import
301 lines
7.9 KiB
JavaScript
301 lines
7.9 KiB
JavaScript
import angular from 'angular';
|
|
import uuidv4 from 'uuid/v4';
|
|
|
|
class AuthenticationController {
|
|
/* @ngInject */
|
|
constructor(
|
|
$async,
|
|
$scope,
|
|
$state,
|
|
$stateParams,
|
|
$sanitize,
|
|
$window,
|
|
Authentication,
|
|
UserService,
|
|
EndpointService,
|
|
ExtensionService,
|
|
StateManager,
|
|
Notifications,
|
|
SettingsService,
|
|
URLHelper,
|
|
LocalStorage,
|
|
StatusService
|
|
) {
|
|
this.$async = $async;
|
|
this.$scope = $scope;
|
|
this.$state = $state;
|
|
this.$stateParams = $stateParams;
|
|
this.$window = $window;
|
|
this.$sanitize = $sanitize;
|
|
this.Authentication = Authentication;
|
|
this.UserService = UserService;
|
|
this.EndpointService = EndpointService;
|
|
this.ExtensionService = ExtensionService;
|
|
this.StateManager = StateManager;
|
|
this.Notifications = Notifications;
|
|
this.SettingsService = SettingsService;
|
|
this.URLHelper = URLHelper;
|
|
this.LocalStorage = LocalStorage;
|
|
this.StatusService = StatusService;
|
|
|
|
this.logo = this.StateManager.getState().application.logo;
|
|
this.formValues = {
|
|
Username: '',
|
|
Password: '',
|
|
};
|
|
this.state = {
|
|
AuthenticationError: '',
|
|
loginInProgress: true,
|
|
OAuthProvider: '',
|
|
};
|
|
|
|
this.retrieveAndSaveEnabledExtensionsAsync = this.retrieveAndSaveEnabledExtensionsAsync.bind(this);
|
|
this.checkForEndpointsAsync = this.checkForEndpointsAsync.bind(this);
|
|
this.checkForLatestVersionAsync = this.checkForLatestVersionAsync.bind(this);
|
|
this.postLoginSteps = this.postLoginSteps.bind(this);
|
|
|
|
this.oAuthLoginAsync = this.oAuthLoginAsync.bind(this);
|
|
this.retryLoginSanitizeAsync = this.retryLoginSanitizeAsync.bind(this);
|
|
this.internalLoginAsync = this.internalLoginAsync.bind(this);
|
|
|
|
this.authenticateUserAsync = this.authenticateUserAsync.bind(this);
|
|
|
|
this.manageOauthCodeReturn = this.manageOauthCodeReturn.bind(this);
|
|
this.authEnabledFlowAsync = this.authEnabledFlowAsync.bind(this);
|
|
this.onInit = this.onInit.bind(this);
|
|
}
|
|
|
|
/**
|
|
* UTILS FUNCTIONS SECTION
|
|
*/
|
|
|
|
logout(error) {
|
|
this.Authentication.logout();
|
|
this.state.loginInProgress = false;
|
|
this.generateOAuthLoginURI();
|
|
this.LocalStorage.storeLogoutReason(error);
|
|
this.$window.location.reload();
|
|
}
|
|
|
|
error(err, message) {
|
|
this.state.AuthenticationError = message;
|
|
if (!err) {
|
|
err = {};
|
|
}
|
|
this.Notifications.error('Failure', err, message);
|
|
this.state.loginInProgress = false;
|
|
}
|
|
|
|
determineOauthProvider(LoginURI) {
|
|
if (LoginURI.indexOf('login.microsoftonline.com') !== -1) {
|
|
return 'Microsoft';
|
|
} else if (LoginURI.indexOf('accounts.google.com') !== -1) {
|
|
return 'Google';
|
|
} else if (LoginURI.indexOf('github.com') !== -1) {
|
|
return 'Github';
|
|
}
|
|
return 'OAuth';
|
|
}
|
|
|
|
generateState() {
|
|
const uuid = uuidv4();
|
|
this.LocalStorage.storeLoginStateUUID(uuid);
|
|
return '&state=' + uuid;
|
|
}
|
|
|
|
generateOAuthLoginURI() {
|
|
this.OAuthLoginURI = this.state.OAuthLoginURI + this.generateState();
|
|
}
|
|
|
|
hasValidState(state) {
|
|
const savedUUID = this.LocalStorage.getLoginStateUUID();
|
|
return savedUUID && state && savedUUID === state;
|
|
}
|
|
|
|
/**
|
|
* END UTILS FUNCTIONS SECTION
|
|
*/
|
|
|
|
/**
|
|
* POST LOGIN STEPS SECTION
|
|
*/
|
|
|
|
async retrieveAndSaveEnabledExtensionsAsync() {
|
|
try {
|
|
await this.ExtensionService.retrieveAndSaveEnabledExtensions();
|
|
} catch (err) {
|
|
this.error(err, 'Unable to retrieve enabled extensions');
|
|
}
|
|
}
|
|
|
|
async checkForEndpointsAsync() {
|
|
try {
|
|
const endpoints = await this.EndpointService.endpoints(0, 1);
|
|
const isAdmin = this.Authentication.isAdmin();
|
|
|
|
if (endpoints.value.length === 0 && isAdmin) {
|
|
return this.$state.go('portainer.init.endpoint');
|
|
} else {
|
|
return this.$state.go('portainer.home');
|
|
}
|
|
} catch (err) {
|
|
this.error(err, 'Unable to retrieve endpoints');
|
|
}
|
|
}
|
|
|
|
async checkForLatestVersionAsync() {
|
|
let versionInfo = {
|
|
UpdateAvailable: false,
|
|
LatestVersion: '',
|
|
};
|
|
|
|
try {
|
|
const versionStatus = await this.StatusService.version();
|
|
if (versionStatus.UpdateAvailable) {
|
|
versionInfo.UpdateAvailable = true;
|
|
versionInfo.LatestVersion = versionStatus.LatestVersion;
|
|
}
|
|
} finally {
|
|
this.StateManager.setVersionInfo(versionInfo);
|
|
}
|
|
}
|
|
|
|
async postLoginSteps() {
|
|
await this.retrieveAndSaveEnabledExtensionsAsync();
|
|
await this.checkForEndpointsAsync();
|
|
await this.checkForLatestVersionAsync();
|
|
}
|
|
/**
|
|
* END POST LOGIN STEPS SECTION
|
|
*/
|
|
|
|
/**
|
|
* LOGIN METHODS SECTION
|
|
*/
|
|
|
|
async oAuthLoginAsync(code) {
|
|
try {
|
|
await this.Authentication.OAuthLogin(code);
|
|
this.URLHelper.cleanParameters();
|
|
} catch (err) {
|
|
this.error(err, 'Unable to login via OAuth');
|
|
}
|
|
}
|
|
|
|
async retryLoginSanitizeAsync(username, password) {
|
|
try {
|
|
await this.internalLoginAsync(this.$sanitize(username), this.$sanitize(password));
|
|
this.$state.go('portainer.updatePassword');
|
|
} catch (err) {
|
|
this.error(err, 'Invalid credentials');
|
|
}
|
|
}
|
|
|
|
async internalLoginAsync(username, password) {
|
|
await this.Authentication.login(username, password);
|
|
await this.postLoginSteps();
|
|
}
|
|
|
|
/**
|
|
* END LOGIN METHODS SECTION
|
|
*/
|
|
|
|
/**
|
|
* AUTHENTICATE USER SECTION
|
|
*/
|
|
|
|
async authenticateUserAsync() {
|
|
try {
|
|
var username = this.formValues.Username;
|
|
var password = this.formValues.Password;
|
|
this.state.loginInProgress = true;
|
|
await this.internalLoginAsync(username, password);
|
|
} catch (err) {
|
|
if (this.state.permissionsError) {
|
|
return;
|
|
}
|
|
// This login retry is necessary to avoid conflicts with databases
|
|
// containing users created before Portainer 1.19.2
|
|
// See https://github.com/portainer/portainer/issues/2199 for more info
|
|
await this.retryLoginSanitizeAsync(username, password);
|
|
}
|
|
}
|
|
|
|
authenticateUser() {
|
|
return this.$async(this.authenticateUserAsync);
|
|
}
|
|
|
|
/**
|
|
* END AUTHENTICATE USER SECTION
|
|
*/
|
|
|
|
/**
|
|
* ON INIT SECTION
|
|
*/
|
|
async manageOauthCodeReturn(code, state) {
|
|
if (this.hasValidState(state)) {
|
|
await this.oAuthLoginAsync(code);
|
|
} else {
|
|
this.error(null, 'Invalid OAuth state, try again.');
|
|
}
|
|
}
|
|
|
|
async authEnabledFlowAsync() {
|
|
try {
|
|
const exists = await this.UserService.administratorExists();
|
|
if (!exists) {
|
|
this.$state.go('portainer.init.admin');
|
|
}
|
|
} catch (err) {
|
|
this.error(err, 'Unable to verify administrator account existence');
|
|
}
|
|
}
|
|
|
|
async onInit() {
|
|
try {
|
|
const settings = await this.SettingsService.publicSettings();
|
|
this.AuthenticationMethod = settings.AuthenticationMethod;
|
|
this.state.OAuthProvider = this.determineOauthProvider(settings.OAuthLoginURI);
|
|
this.state.OAuthLoginURI = settings.OAuthLoginURI;
|
|
|
|
const code = this.URLHelper.getParameter('code');
|
|
const state = this.URLHelper.getParameter('state');
|
|
if (code && state) {
|
|
await this.manageOauthCodeReturn(code, state);
|
|
this.generateOAuthLoginURI();
|
|
return;
|
|
}
|
|
this.generateOAuthLoginURI();
|
|
|
|
if (this.$stateParams.logout || this.$stateParams.error) {
|
|
this.logout(this.$stateParams.error);
|
|
return;
|
|
}
|
|
const error = this.LocalStorage.getLogoutReason();
|
|
if (error) {
|
|
this.state.AuthenticationError = error;
|
|
this.LocalStorage.cleanLogoutReason();
|
|
}
|
|
|
|
if (this.Authentication.isAuthenticated()) {
|
|
await this.postLoginSteps();
|
|
}
|
|
this.state.loginInProgress = false;
|
|
|
|
await this.authEnabledFlowAsync();
|
|
} catch (err) {
|
|
this.Notifications.error('Failure', err, 'Unable to retrieve public settings');
|
|
}
|
|
}
|
|
|
|
$onInit() {
|
|
return this.$async(this.onInit);
|
|
}
|
|
|
|
/**
|
|
* END ON INIT SECTION
|
|
*/
|
|
}
|
|
|
|
export default AuthenticationController;
|
|
angular.module('portainer.app').controller('AuthenticationController', AuthenticationController);
|