diff options
Diffstat (limited to 'app/assets/javascripts/jira_connect/subscriptions/components')
3 files changed, 104 insertions, 28 deletions
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue index 66aea60c5b5..22a6c0751f4 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue @@ -83,7 +83,7 @@ export default { * if the jiraConnectOauth flag is enabled. */ fetchSubscriptionsOauth() { - if (!this.isOauthEnabled) return; + if (!this.isOauthEnabled || !this.userSignedIn) return; this.fetchSubscriptions(this.subscriptionsPath); }, @@ -146,12 +146,12 @@ export default { <div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7"> <sign-in-page - v-if="!userSignedIn" + v-show="!userSignedIn" :has-subscriptions="hasSubscriptions" @sign-in-oauth="onSignInOauth" @error="onSignInError" /> - <subscriptions-page v-else :has-subscriptions="hasSubscriptions" /> + <subscriptions-page v-if="userSignedIn" :has-subscriptions="hasSubscriptions" /> </div> </div> </main> diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue index c5b56535247..9b50681515e 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue @@ -3,6 +3,7 @@ import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; import { s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; const COMPATIBILITY_ALERT_STATE_KEY = 'compatibility_alert_dismissed'; @@ -14,6 +15,7 @@ export default { GlLink, LocalStorageSync, }, + mixins: [glFeatureFlagMixin()], data() { return { alertDismissed: false, @@ -23,6 +25,14 @@ export default { shouldShowAlert() { return !this.alertDismissed; }, + isOauthSelfManagedEnabled() { + return this.glFeatures.jiraConnectOauth && this.glFeatures.jiraConnectOauthSelfManaged; + }, + alertBody() { + return this.isOauthSelfManagedEnabled + ? this.$options.i18n.body + : this.$options.i18n.bodyDotCom; + }, }, methods: { dismissAlert() { @@ -32,6 +42,9 @@ export default { i18n: { title: s__('Integrations|Known limitations'), body: s__( + 'Integrations|Adding a namespace only works in browsers that allow cross-site cookies. %{linkStart}Learn more%{linkEnd}.', + ), + bodyDotCom: s__( 'Integrations|This integration only works with GitLab.com. Adding a namespace only works in browsers that allow cross-site cookies. %{linkStart}Learn more%{linkEnd}.', ), }, @@ -50,7 +63,7 @@ export default { :title="$options.i18n.title" @dismiss="dismissAlert" > - <gl-sprintf :message="$options.i18n.body"> + <gl-sprintf :message="alertBody"> <template #link="{ content }"> <gl-link :href="$options.DOCS_LINK_URL" target="_blank">{{ content }}</gl-link> </template> diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue index ad3e70bcb5f..4cf3a1a0279 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue @@ -1,30 +1,53 @@ <script> import { mapActions, mapMutations } from 'vuex'; import { GlButton } from '@gitlab/ui'; -import axios from '~/lib/utils/axios_utils'; +import { sprintf } from '~/locale'; + import { I18N_DEFAULT_SIGN_IN_BUTTON_TEXT, + I18N_CUSTOM_SIGN_IN_BUTTON_TEXT, + I18N_OAUTH_APPLICATION_ID_ERROR_MESSAGE, + I18N_OAUTH_FAILED_TITLE, + I18N_OAUTH_FAILED_MESSAGE, + OAUTH_SELF_MANAGED_DOC_LINK, OAUTH_WINDOW_OPTIONS, PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM, } from '~/jira_connect/subscriptions/constants'; +import { fetchOAuthApplicationId, fetchOAuthToken } from '~/jira_connect/subscriptions/api'; import { setUrlParams } from '~/lib/utils/url_utility'; import AccessorUtilities from '~/lib/utils/accessor'; import { createCodeVerifier, createCodeChallenge } from '../pkce'; -import { SET_ACCESS_TOKEN } from '../store/mutation_types'; +import { SET_ACCESS_TOKEN, SET_ALERT } from '../store/mutation_types'; export default { components: { GlButton, }, inject: ['oauthMetadata'], + props: { + gitlabBasePath: { + type: String, + required: false, + default: undefined, + }, + }, data() { return { - token: null, loading: false, codeVerifier: null, + clientId: null, canUseCrypto: AccessorUtilities.canUseCrypto(), }; }, + computed: { + buttonText() { + if (!this.gitlabBasePath) { + return I18N_DEFAULT_SIGN_IN_BUTTON_TEXT; + } + + return sprintf(I18N_CUSTOM_SIGN_IN_BUTTON_TEXT, { url: this.gitlabBasePath }); + }, + }, created() { window.addEventListener('message', this.handleWindowMessage); }, @@ -35,30 +58,72 @@ export default { ...mapActions(['loadCurrentUser']), ...mapMutations({ setAccessToken: SET_ACCESS_TOKEN, + setAlert: SET_ALERT, }), - async startOAuthFlow() { - this.loading = true; - + async fetchOauthClientId() { + const { + data: { application_id: clientId }, + } = await fetchOAuthApplicationId(); + return clientId; + }, + async getOauthAuthorizeURL() { // Generate state necessary for PKCE OAuth flow this.codeVerifier = createCodeVerifier(); const codeChallenge = await createCodeChallenge(this.codeVerifier); + try { + this.clientId = this.gitlabBasePath + ? await this.fetchOauthClientId() + : this.oauthMetadata?.oauth_token_payload?.client_id; + } catch { + throw new Error(I18N_OAUTH_APPLICATION_ID_ERROR_MESSAGE); + } // Build the initial OAuth authorization URL const { oauth_authorize_url: oauthAuthorizeURL } = this.oauthMetadata; - - const oauthAuthorizeURLWithChallenge = setUrlParams( - { - code_challenge: codeChallenge, - code_challenge_method: PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM.short, - }, - oauthAuthorizeURL, + const oauthAuthorizeURLWithChallenge = new URL( + setUrlParams( + { + code_challenge: codeChallenge, + code_challenge_method: PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM.short, + client_id: this.clientId, + }, + oauthAuthorizeURL, + ), ); - window.open( - oauthAuthorizeURLWithChallenge, - this.$options.i18n.defaultButtonText, - OAUTH_WINDOW_OPTIONS, - ); + // Rebase URL on the specified GitLab base path (if specified). + if (this.gitlabBasePath) { + const gitlabBasePathURL = new URL(this.gitlabBasePath); + oauthAuthorizeURLWithChallenge.hostname = gitlabBasePathURL.hostname; + oauthAuthorizeURLWithChallenge.pathname = `${ + gitlabBasePathURL.pathname === '/' ? '' : gitlabBasePathURL.pathname + }${oauthAuthorizeURLWithChallenge.pathname}`; + } + + return oauthAuthorizeURLWithChallenge.toString(); + }, + async startOAuthFlow() { + try { + this.loading = true; + const oauthAuthorizeURL = await this.getOauthAuthorizeURL(); + + window.open(oauthAuthorizeURL, I18N_DEFAULT_SIGN_IN_BUTTON_TEXT, OAUTH_WINDOW_OPTIONS); + } catch (e) { + if (e.message) { + this.setAlert({ + message: e.message, + variant: 'danger', + }); + } else { + this.setAlert({ + linkUrl: OAUTH_SELF_MANAGED_DOC_LINK, + title: I18N_OAUTH_FAILED_TITLE, + message: this.gitlabBasePath ? I18N_OAUTH_FAILED_MESSAGE : '', + variant: 'danger', + }); + } + this.loading = false; + } }, async handleWindowMessage(event) { if (window.origin !== event.origin) { @@ -94,20 +159,18 @@ export default { async getOAuthToken(code) { const { oauth_token_payload: oauthTokenPayload, - oauth_token_url: oauthTokenURL, + oauth_token_path: oauthTokenPath, } = this.oauthMetadata; - const { data } = await axios.post(oauthTokenURL, { + const { data } = await fetchOAuthToken(oauthTokenPath, { ...oauthTokenPayload, code, code_verifier: this.codeVerifier, + client_id: this.clientId, }); return data.access_token; }, }, - i18n: { - defaultButtonText: I18N_DEFAULT_SIGN_IN_BUTTON_TEXT, - }, }; </script> <template> @@ -119,7 +182,7 @@ export default { @click="startOAuthFlow" > <slot> - {{ $options.i18n.defaultButtonText }} + {{ buttonText }} </slot> </gl-button> </template> |