diff --git a/assets/translations/en-us.yaml b/assets/translations/en-us.yaml index e89103dde1..6636fbf85a 100644 --- a/assets/translations/en-us.yaml +++ b/assets/translations/en-us.yaml @@ -2548,7 +2548,7 @@ probe: placeholder: Select a check type project: - members: + members: label: Members user: User role: Role @@ -3098,26 +3098,24 @@ servicesPage: label: Service Type setup: - welcome: Welcome to {vendor}! - setPassword: The first order of business is to set a strong password for the default admin user. We suggest using this random one generated just for you, but enter your own if you like. - newPassword: New Password confirmPassword: Confirm New Password - useRandom: Use a randomly generated password - useManual: Set a specific password to use defaultPasswordError: It looks like this is your first time visting the Rancher UI, but the local admin account password is already set to something unique. Log in with that account below to continue the setup process. - telemetry: - label: Allow collection of anonymous statistics to help us improve Rancher - tip: 'Rancher Labs would like to collect a bit of anonymized information - about the configuration of your installation to help make Rio better. - Your data will not be shared with anyone else, and no information about - what specific resources or endpoints you are deploying is included. - Once enabled you can view exactly what data will be sent at /v1-telemetry. - More Info' eula: I agree to the terms and conditions for using Rancher. + newPassword: New Password + newUserSetPassword: The first order of business is to set a strong password for {username}. We suggest using this random one generated just for you, but enter your own if you like. serverUrl: label: Server URL - tip: What URL should be used for this Rancher installation? All the nodes in your clusters will need to be able to reach this. You can skip setting this for now, and update it later in General Settings>Advanced Settings. skip: Skip + tip: What URL should be used for this Rancher installation? All the nodes in your clusters will need to be able to reach this. You can skip setting this for now, and update it later in General Settings>Advanced Settings. + setPassword: The first order of business is to set a strong password for the default {username} user. We suggest using this random one generated just for you, but enter your own if you like. + telemetry: + label: Allow collection of anonymous statistics to help us improve Rancher + tip: >- + Rancher Labs would like to collect a bit of anonymized information about the configuration of your installation to help make Rio better. Your data will not be shared with anyone else, and no information about what specific resources or endpoints you are deploying is included. Once enabled you can view exactly what data will be sent at /v1-telemetry. More + Info + useManual: Set a specific password to use + useRandom: Use a randomly generated password + welcome: Welcome to {vendor}! sortableTable: actionAvailability: diff --git a/config/query-params.js b/config/query-params.js index aac89a2acd..11237a0e2a 100644 --- a/config/query-params.js +++ b/config/query-params.js @@ -13,6 +13,7 @@ export const GITHUB_CODE = 'code'; export const GITHUB_NONCE = 'state'; export const GITHUB_SCOPE = 'scope'; export const GITHUB_REDIRECT = 'redirect_uri'; +export const USERNAME = 'un'; // General export const _FLAGGED = null; // The value for a key-only flag, like `?desc` diff --git a/middleware/authenticated.js b/middleware/authenticated.js index f436df63bc..e07a1753b7 100644 --- a/middleware/authenticated.js +++ b/middleware/authenticated.js @@ -7,6 +7,7 @@ import { applyProducts } from '@/store/type-map'; import { findBy } from '@/utils/array'; import { ClusterNotFoundError } from '@/utils/error'; import { get } from '@/utils/object'; +import { isEmpty } from 'lodash'; let beforeEachSetup = false; @@ -93,10 +94,10 @@ export default async function({ if (ok) { if (initialPass) { - return redirect({ name: 'auth-setup', query: { [SETUP]: initialPass } }); - } else { - return redirect({ name: 'auth-setup' }); + store.dispatch('auth/setInitialPass', initialPass); } + + return redirect({ name: 'auth-setup' }); } else { const t = store.getters['i18n/t']; @@ -125,6 +126,14 @@ export default async function({ } if ( store.getters['auth/enabled'] !== false && !store.getters['auth/loggedIn'] ) { + const v3User = await findV3User(store); + + if (v3User?.mustChangePassword) { + store.commit('auth/gotUser', v3User); + + return redirect({ name: 'auth-setup' }); + } + // In newer versions the API calls return the auth state instead of having to make a new call all the time. const fromHeader = store.getters['auth/fromHeader']; @@ -228,6 +237,15 @@ async function findMe(store) { return me; } +async function findV3User(store) { + const user = await store.dispatch('rancher/findAll', { + type: NORMAN.USER, + opt: { url: '/v3/users?me=true' } + }); + + return isEmpty(user[0]) ? {} : user[0]; +} + async function tryInitialSetup(store, password = 'admin') { try { const res = await store.dispatch('auth/login', { diff --git a/pages/auth/login.vue b/pages/auth/login.vue index 710b56b7c2..11f911b520 100644 --- a/pages/auth/login.vue +++ b/pages/auth/login.vue @@ -11,9 +11,10 @@ import { configType } from '@/models/management.cattle.io.authconfig'; import { mapGetters } from 'vuex'; import { importLogin } from '@/utils/dynamic-importer'; import { _ALL_IF_AUTHED } from '@/plugins/steve/actions'; -import { MANAGEMENT } from '@/config/types'; +import { MANAGEMENT, NORMAN } from '@/config/types'; import { SETTING } from '@/config/settings'; import { LOGIN_ERRORS } from '@/store/auth'; +import isEmpty from 'lodash/isEmpty'; import { getVendor, getProduct, setVendor } from '../../config/private-label'; export default { @@ -190,6 +191,18 @@ export default { password: this.password } }); + + const user = await this.$store.dispatch('rancher/findAll', { + type: NORMAN.USER, + opt: { url: '/v3/users?me=true' } + }); + + if (!isEmpty(user) && !isEmpty(user[0])) { + this.$store.dispatch('auth/gotUser', user[0]); + + this.needsSetup = user[0]?.mustChangePassword ?? false; + } + if ( this.remember ) { this.$cookies.set(USERNAME, this.username, { encode: x => x, @@ -202,7 +215,8 @@ export default { } if (this.needsSetup) { - this.$router.push({ name: 'auth-setup', query: { setup: this.password } }); + this.$store.dispatch('auth/setInitialPass', this.password); + this.$router.push({ name: 'auth-setup' }); } else { this.$router.replace('/'); } diff --git a/pages/auth/setup.vue b/pages/auth/setup.vue index c8f7e95868..e62b49e9b7 100644 --- a/pages/auth/setup.vue +++ b/pages/auth/setup.vue @@ -28,8 +28,9 @@ export default { } const firstLoginSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.FIRST_LOGIN); + const v3User = store.getters['auth/v3User'] ?? {}; - if (firstLoginSetting?.value !== 'true') { + if (firstLoginSetting?.value !== 'true' && !v3User?.mustChangePassword) { return redirect('/'); } }, @@ -51,8 +52,10 @@ export default { } const principals = await store.dispatch('rancher/findAll', { type: NORMAN.PRINCIPAL, opt: { url: '/v3/principals' } }); + const me = findBy(principals, 'me', true); - const current = route.query[SETUP] || 'admin'; + const current = route.query[SETUP] || store.getters['auth/initialPass'] || 'admin'; + const v3User = store.getters['auth/v3User'] ?? {}; let serverUrl; @@ -69,12 +72,15 @@ export default { product: getProduct(), step: parseInt(route.query.step, 10) || 1, - useRandom: false, - haveCurrent: !!current, - username: 'admin', + useRandom: false, + haveCurrent: !!current, + username: me?.loginName ?? 'admin', + mustChangePassword: v3User?.mustChangePassword ?? false, current, - password: '', - confirm: '', + password: '', + confirm: '', + + v3User, serverUrl, @@ -89,7 +95,7 @@ export default { computed: { passwordSubmitDisabled() { - if (!this.eula) { + if (!this.eula && !this.mustChangePassword) { return true; } @@ -138,13 +144,15 @@ export default { methods: { async finishPassword(buttonCb) { try { - await this.$store.dispatch('loadManagement'); + if (!this.mustChangePassword) { + await this.$store.dispatch('loadManagement'); - await Promise.all([ - setSetting(this.$store, SETTING.EULA_AGREED, (new Date()).toISOString() ), - setSetting(this.$store, SETTING.TELEMETRY, this.telemetry ? 'in' : 'out'), - setSetting(this.$store, SETTING.FIRST_LOGIN, 'false'), - ]); + await Promise.all([ + setSetting(this.$store, SETTING.EULA_AGREED, (new Date()).toISOString() ), + setSetting(this.$store, SETTING.TELEMETRY, this.telemetry ? 'in' : 'out'), + setSetting(this.$store, SETTING.FIRST_LOGIN, 'false'), + ]); + } await this.$store.dispatch('rancher/request', { url: '/v3/users?action=changepassword', @@ -154,8 +162,20 @@ export default { newPassword: this.password }, }); - this.step = 2; - buttonCb(true); + + if (this.mustChangePassword) { + const user = this.v3User; + + user.mustChangePassword = false; + this.$store.dispatch('auth/gotUser', user); + + buttonCb(true); + + this.done(); + } else { + this.step = 2; + buttonCb(true); + } } catch (err) { buttonCb(false); } @@ -186,9 +206,10 @@ export default {