Welcome to mirror list, hosted at ThFree Co, Russian Federation.

sign_in_oauth_button.vue « components « subscriptions « jira_connect « javascripts « assets « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: ad3e70bcb5f2a636ee6eeadb3af064342c9448bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<script>
import { mapActions, mapMutations } from 'vuex';
import { GlButton } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import {
  I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
  OAUTH_WINDOW_OPTIONS,
  PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM,
} from '~/jira_connect/subscriptions/constants';
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';

export default {
  components: {
    GlButton,
  },
  inject: ['oauthMetadata'],
  data() {
    return {
      token: null,
      loading: false,
      codeVerifier: null,
      canUseCrypto: AccessorUtilities.canUseCrypto(),
    };
  },
  created() {
    window.addEventListener('message', this.handleWindowMessage);
  },
  beforeDestroy() {
    window.removeEventListener('message', this.handleWindowMessage);
  },
  methods: {
    ...mapActions(['loadCurrentUser']),
    ...mapMutations({
      setAccessToken: SET_ACCESS_TOKEN,
    }),
    async startOAuthFlow() {
      this.loading = true;

      // Generate state necessary for PKCE OAuth flow
      this.codeVerifier = createCodeVerifier();
      const codeChallenge = await createCodeChallenge(this.codeVerifier);

      // 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,
      );

      window.open(
        oauthAuthorizeURLWithChallenge,
        this.$options.i18n.defaultButtonText,
        OAUTH_WINDOW_OPTIONS,
      );
    },
    async handleWindowMessage(event) {
      if (window.origin !== event.origin) {
        this.loading = false;
        return;
      }

      // Verify that OAuth state isn't altered.
      const state = event.data?.state;
      if (state !== this.oauthMetadata.state) {
        this.loading = false;
        this.handleError();
        return;
      }

      // Request access token and load the authenticated user.
      const code = event.data?.code;
      try {
        const accessToken = await this.getOAuthToken(code);
        await this.loadCurrentUser(accessToken);

        this.setAccessToken(accessToken);
        this.$emit('sign-in');
      } catch (e) {
        this.handleError();
      } finally {
        this.loading = false;
      }
    },
    handleError() {
      this.$emit('error');
    },
    async getOAuthToken(code) {
      const {
        oauth_token_payload: oauthTokenPayload,
        oauth_token_url: oauthTokenURL,
      } = this.oauthMetadata;
      const { data } = await axios.post(oauthTokenURL, {
        ...oauthTokenPayload,
        code,
        code_verifier: this.codeVerifier,
      });

      return data.access_token;
    },
  },
  i18n: {
    defaultButtonText: I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
  },
};
</script>
<template>
  <gl-button
    v-bind="$attrs"
    variant="info"
    :loading="loading"
    :disabled="!canUseCrypto"
    @click="startOAuthFlow"
  >
    <slot>
      {{ $options.i18n.defaultButtonText }}
    </slot>
  </gl-button>
</template>