mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
New OpenID and OAuth2 drivers (#8660)
* Moved over oauth impl to new interface * Fixed most build issues and started addind schema to auth drivers * Finished up OAuth2 and OpenID drivers * Removed unused migration and utils * Fixed minor todos * Removed old oauth flow * Changed oauth flow to re-use refresh token * Added new oauth frontend * Added font awesome social icons * Updated authentication documentation * Update api/src/auth/drivers/oauth2.ts * Tested implementation and fixed incorrect validation * Updated docs * Improved OAuth error handling and re-enabled creating users with provider/identifier * Removed Session config from docs * Update app/src/components/v-icon/v-icon.vue * Removed oauth need to define default roleID * Added FormatTitle to SSO links * Prevent local auth without password * Store OAuth access token in session data * Update docs/guides/api-config.md * Fixed copy and removed fontawesome-vue dependency * More docs fixes * Crucialy importend type fiks * Update package-lock * Remove is-email-allowed check In favor of more advanced version based on filtering coming later * Fix JSON type casting * Delete unused util * Update type signature to include name * Add warning when code isn't found in oauth url and remove obsolete imports * Auto-continue on successful SSO login * Tweak type signature * More type casting shenanigans * Please the TS gods * Check for missing token before crashing Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
@@ -8,12 +8,15 @@
|
||||
@click="emitClick"
|
||||
>
|
||||
<component :is="customIconName" v-if="customIconName" />
|
||||
<socialIcon v-else-if="socialIconName" :name="socialIconName" />
|
||||
<i v-else :class="{ filled }">{{ name }}</i>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { defineComponent, computed, h, PropType } from 'vue';
|
||||
import { library, icon, findIconDefinition, IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons';
|
||||
import useSizeClass, { sizeProps } from '@/composables/size-class';
|
||||
|
||||
import CustomIconDirectus from './custom-icons/directus.vue';
|
||||
@@ -35,6 +38,8 @@ import CustomIconFolderMove from './custom-icons/folder_move.vue';
|
||||
import CustomIconFolderLock from './custom-icons/folder_lock.vue';
|
||||
import CustomIconLogout from './custom-icons/logout.vue';
|
||||
|
||||
library.add(fab);
|
||||
|
||||
const customIcons: string[] = [
|
||||
'directus',
|
||||
'bookmark_save',
|
||||
@@ -56,6 +61,478 @@ const customIcons: string[] = [
|
||||
'logout',
|
||||
];
|
||||
|
||||
const socialIcons: string[] = [
|
||||
'500px',
|
||||
'accessible_icon',
|
||||
'accusoft',
|
||||
'acquisitions_incorporated',
|
||||
'adn',
|
||||
'adversal',
|
||||
'affiliatetheme',
|
||||
'airbnb',
|
||||
'algolia',
|
||||
'alipay',
|
||||
'amazon',
|
||||
'amazon_pay',
|
||||
'amilia',
|
||||
'android',
|
||||
'angellist',
|
||||
'angrycreative',
|
||||
'angular',
|
||||
'app_store',
|
||||
'app_store_ios',
|
||||
'apper',
|
||||
'apple',
|
||||
'apple_pay',
|
||||
'artstation',
|
||||
'asymmetrik',
|
||||
'atlassian',
|
||||
'audible',
|
||||
'autoprefixer',
|
||||
'avianex',
|
||||
'aviato',
|
||||
'aws',
|
||||
'bandcamp',
|
||||
'battle_net',
|
||||
'behance',
|
||||
'behance_square',
|
||||
'bimobject',
|
||||
'bitbucket',
|
||||
'bitcoin',
|
||||
'bity',
|
||||
'black_tie',
|
||||
'blackberry',
|
||||
'blogger',
|
||||
'blogger_b',
|
||||
'bluetooth',
|
||||
'bluetooth_b',
|
||||
'bootstrap',
|
||||
'btc',
|
||||
'buffer',
|
||||
'buromobelexperte',
|
||||
'buy_n_large',
|
||||
'buysellads',
|
||||
'canadian_maple_leaf',
|
||||
'cc_amazon_pay',
|
||||
'cc_amex',
|
||||
'cc_apple_pay',
|
||||
'cc_diners_club',
|
||||
'cc_discover',
|
||||
'cc_jcb',
|
||||
'cc_mastercard',
|
||||
'cc_paypal',
|
||||
'cc_stripe',
|
||||
'cc_visa',
|
||||
'centercode',
|
||||
'centos',
|
||||
'chrome',
|
||||
'chromecast',
|
||||
'cloudflare',
|
||||
'cloudscale',
|
||||
'cloudsmith',
|
||||
'cloudversify',
|
||||
'codepen',
|
||||
'codiepie',
|
||||
'confluence',
|
||||
'connectdevelop',
|
||||
'contao',
|
||||
'cotton_bureau',
|
||||
'cpanel',
|
||||
'creative_commons',
|
||||
'creative_commons_by',
|
||||
'creative_commons_nc',
|
||||
'creative_commons_nc_eu',
|
||||
'creative_commons_nc_jp',
|
||||
'creative_commons_nd',
|
||||
'creative_commons_pd',
|
||||
'creative_commons_pd_alt',
|
||||
'creative_commons_remix',
|
||||
'creative_commons_sa',
|
||||
'creative_commons_sampling',
|
||||
'creative_commons_sampling_plus',
|
||||
'creative_commons_share',
|
||||
'creative_commons_zero',
|
||||
'critical_role',
|
||||
'css3',
|
||||
'css3_alt',
|
||||
'cuttlefish',
|
||||
'd_and_d',
|
||||
'd_and_d_beyond',
|
||||
'dailymotion',
|
||||
'dashcube',
|
||||
'deezer',
|
||||
'delicious',
|
||||
'deploydog',
|
||||
'deskpro',
|
||||
'dev',
|
||||
'deviantart',
|
||||
'dhl',
|
||||
'diaspora',
|
||||
'digg',
|
||||
'digital_ocean',
|
||||
'discord',
|
||||
'discourse',
|
||||
'dochub',
|
||||
'docker',
|
||||
'draft2digital',
|
||||
'dribbble',
|
||||
'dribbble_square',
|
||||
'dropbox',
|
||||
'drupal',
|
||||
'dyalog',
|
||||
'earlybirds',
|
||||
'ebay',
|
||||
'edge',
|
||||
'edge_legacy',
|
||||
'elementor',
|
||||
'ello',
|
||||
'ember',
|
||||
'empire',
|
||||
'envira',
|
||||
'erlang',
|
||||
'ethereum',
|
||||
'etsy',
|
||||
'evernote',
|
||||
'expeditedssl',
|
||||
'facebook',
|
||||
'facebook_f',
|
||||
'facebook_messenger',
|
||||
'facebook_square',
|
||||
'fantasy_flight_games',
|
||||
'fedex',
|
||||
'fedora',
|
||||
'figma',
|
||||
'firefox',
|
||||
'firefox_browser',
|
||||
'first_order',
|
||||
'first_order_alt',
|
||||
'firstdraft',
|
||||
'flickr',
|
||||
'flipboard',
|
||||
'fly',
|
||||
'font_awesome',
|
||||
'font_awesome_alt',
|
||||
'font_awesome_flag',
|
||||
'fonticons',
|
||||
'fonticons_fi',
|
||||
'fort_awesome',
|
||||
'fort_awesome_alt',
|
||||
'forumbee',
|
||||
'foursquare',
|
||||
'free_code_camp',
|
||||
'freebsd',
|
||||
'fulcrum',
|
||||
'galactic_republic',
|
||||
'galactic_senate',
|
||||
'get_pocket',
|
||||
'gg',
|
||||
'gg_circle',
|
||||
'git',
|
||||
'git_alt',
|
||||
'git_square',
|
||||
'github',
|
||||
'github_alt',
|
||||
'github_square',
|
||||
'gitkraken',
|
||||
'gitlab',
|
||||
'gitter',
|
||||
'glide',
|
||||
'glide_g',
|
||||
'gofore',
|
||||
'goodreads',
|
||||
'goodreads_g',
|
||||
'google',
|
||||
'google_drive',
|
||||
'google_pay',
|
||||
'google_play',
|
||||
'google_plus',
|
||||
'google_plus_g',
|
||||
'google_plus_square',
|
||||
'google_wallet',
|
||||
'gratipay',
|
||||
'grav',
|
||||
'gripfire',
|
||||
'grunt',
|
||||
'guilded',
|
||||
'gulp',
|
||||
'hacker_news',
|
||||
'hacker_news_square',
|
||||
'hackerrank',
|
||||
'hips',
|
||||
'hire_a_helper',
|
||||
'hive',
|
||||
'hooli',
|
||||
'hornbill',
|
||||
'hotjar',
|
||||
'houzz',
|
||||
'html5',
|
||||
'hubspot',
|
||||
'ideal',
|
||||
'imdb',
|
||||
'innosoft',
|
||||
'instagram',
|
||||
'instagram_square',
|
||||
'instalod',
|
||||
'intercom',
|
||||
'internet_explorer',
|
||||
'invision',
|
||||
'ioxhost',
|
||||
'itch_io',
|
||||
'itunes',
|
||||
'itunes_note',
|
||||
'java',
|
||||
'jedi_order',
|
||||
'jenkins',
|
||||
'jira',
|
||||
'joget',
|
||||
'joomla',
|
||||
'js',
|
||||
'js_square',
|
||||
'jsfiddle',
|
||||
'kaggle',
|
||||
'keybase',
|
||||
'keycdn',
|
||||
'kickstarter',
|
||||
'kickstarter_k',
|
||||
'korvue',
|
||||
'laravel',
|
||||
'lastfm',
|
||||
'lastfm_square',
|
||||
'leanpub',
|
||||
'less',
|
||||
'line',
|
||||
'linkedin',
|
||||
'linkedin_in',
|
||||
'linode',
|
||||
'linux',
|
||||
'lyft',
|
||||
'magento',
|
||||
'mailchimp',
|
||||
'mandalorian',
|
||||
'markdown',
|
||||
'mastodon',
|
||||
'maxcdn',
|
||||
'mdb',
|
||||
'medapps',
|
||||
'medium',
|
||||
'medium_m',
|
||||
'medrt',
|
||||
'meetup',
|
||||
'megaport',
|
||||
'mendeley',
|
||||
'microblog',
|
||||
'microsoft',
|
||||
'mix',
|
||||
'mixcloud',
|
||||
'mixer',
|
||||
'mizuni',
|
||||
'modx',
|
||||
'monero',
|
||||
'napster',
|
||||
'neos',
|
||||
'nimblr',
|
||||
'node',
|
||||
'node_js',
|
||||
'npm',
|
||||
'ns8',
|
||||
'nutritionix',
|
||||
'octopus_deploy',
|
||||
'odnoklassniki',
|
||||
'odnoklassniki_square',
|
||||
'old_republic',
|
||||
'opencart',
|
||||
'openid',
|
||||
'opera',
|
||||
'optin_monster',
|
||||
'orcid',
|
||||
'osi',
|
||||
'page4',
|
||||
'pagelines',
|
||||
'palfed',
|
||||
'patreon',
|
||||
'paypal',
|
||||
'penny_arcade',
|
||||
'perbyte',
|
||||
'periscope',
|
||||
'phabricator',
|
||||
'phoenix_framework',
|
||||
'phoenix_squadron',
|
||||
'php',
|
||||
'pied_piper',
|
||||
'pied_piper_alt',
|
||||
'pied_piper_hat',
|
||||
'pied_piper_pp',
|
||||
'pied_piper_square',
|
||||
'pinterest',
|
||||
'pinterest_p',
|
||||
'pinterest_square',
|
||||
'playstation',
|
||||
'product_hunt',
|
||||
'pushed',
|
||||
'python',
|
||||
'qq',
|
||||
'quinscape',
|
||||
'quora',
|
||||
'r_project',
|
||||
'raspberry_pi',
|
||||
'ravelry',
|
||||
'react',
|
||||
'reacteurope',
|
||||
'readme',
|
||||
'rebel',
|
||||
'red_river',
|
||||
'reddit',
|
||||
'reddit_alien',
|
||||
'reddit_square',
|
||||
'redhat',
|
||||
'renren',
|
||||
'replyd',
|
||||
'researchgate',
|
||||
'resolving',
|
||||
'rev',
|
||||
'rocketchat',
|
||||
'rockrms',
|
||||
'rust',
|
||||
'safari',
|
||||
'salesforce',
|
||||
'sass',
|
||||
'schlix',
|
||||
'scribd',
|
||||
'searchengin',
|
||||
'sellcast',
|
||||
'sellsy',
|
||||
'servicestack',
|
||||
'shirtsinbulk',
|
||||
'shopify',
|
||||
'shopware',
|
||||
'simplybuilt',
|
||||
'sistrix',
|
||||
'sith',
|
||||
'sketch',
|
||||
'skyatlas',
|
||||
'skype',
|
||||
'slack',
|
||||
'slack_hash',
|
||||
'slideshare',
|
||||
'snapchat',
|
||||
'snapchat_ghost',
|
||||
'snapchat_square',
|
||||
'soundcloud',
|
||||
'sourcetree',
|
||||
'speakap',
|
||||
'speaker_deck',
|
||||
'spotify',
|
||||
'squarespace',
|
||||
'stack_exchange',
|
||||
'stack_overflow',
|
||||
'stackpath',
|
||||
'staylinked',
|
||||
'steam',
|
||||
'steam_square',
|
||||
'steam_symbol',
|
||||
'sticker_mule',
|
||||
'strava',
|
||||
'stripe',
|
||||
'stripe_s',
|
||||
'studiovinari',
|
||||
'stumbleupon',
|
||||
'stumbleupon_circle',
|
||||
'superpowers',
|
||||
'supple',
|
||||
'suse',
|
||||
'swift',
|
||||
'symfony',
|
||||
'teamspeak',
|
||||
'telegram',
|
||||
'telegram_plane',
|
||||
'tencent_weibo',
|
||||
'the_red_yeti',
|
||||
'themeco',
|
||||
'themeisle',
|
||||
'think_peaks',
|
||||
'tiktok',
|
||||
'trade_federation',
|
||||
'trello',
|
||||
'tumblr',
|
||||
'tumblr_square',
|
||||
'twitch',
|
||||
'twitter',
|
||||
'twitter_square',
|
||||
'typo3',
|
||||
'uber',
|
||||
'ubuntu',
|
||||
'uikit',
|
||||
'umbraco',
|
||||
'uncharted',
|
||||
'uniregistry',
|
||||
'unity',
|
||||
'unsplash',
|
||||
'untappd',
|
||||
'ups',
|
||||
'usb',
|
||||
'usps',
|
||||
'ussunnah',
|
||||
'vaadin',
|
||||
'viacoin',
|
||||
'viadeo',
|
||||
'viadeo_square',
|
||||
'viber',
|
||||
'vimeo',
|
||||
'vimeo_square',
|
||||
'vimeo_v',
|
||||
'vine',
|
||||
'vk',
|
||||
'vnv',
|
||||
'vuejs',
|
||||
'watchman_monitoring',
|
||||
'waze',
|
||||
'weebly',
|
||||
'weibo',
|
||||
'weixin',
|
||||
'whatsapp',
|
||||
'whatsapp_square',
|
||||
'whmcs',
|
||||
'wikipedia_w',
|
||||
'windows',
|
||||
'wix',
|
||||
'wizards_of_the_coast',
|
||||
'wodu',
|
||||
'wolf_pack_battalion',
|
||||
'wordpress',
|
||||
'wordpress_simple',
|
||||
'wpbeginner',
|
||||
'wpexplorer',
|
||||
'wpforms',
|
||||
'wpressr',
|
||||
'xbox',
|
||||
'xing',
|
||||
'xing_square',
|
||||
'y_combinator',
|
||||
'yahoo',
|
||||
'yammer',
|
||||
'yandex',
|
||||
'yandex_international',
|
||||
'yarn',
|
||||
'yelp',
|
||||
'yoast',
|
||||
'youtube',
|
||||
'youtube_square',
|
||||
'zhihu',
|
||||
];
|
||||
|
||||
const SocialIcon = defineComponent({
|
||||
props: {
|
||||
name: {
|
||||
type: String as PropType<IconName>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const socialIcon = icon(findIconDefinition({ prefix: 'fab', iconName: this.name }));
|
||||
return h({ template: socialIcon?.html[0] });
|
||||
},
|
||||
});
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
CustomIconDirectus,
|
||||
@@ -76,6 +553,7 @@ export default defineComponent({
|
||||
CustomIconFolderMove,
|
||||
CustomIconFolderLock,
|
||||
CustomIconLogout,
|
||||
SocialIcon,
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
@@ -125,9 +603,15 @@ export default defineComponent({
|
||||
return null;
|
||||
});
|
||||
|
||||
const socialIconName = computed<string | null>(() => {
|
||||
if (socialIcons.includes(props.name)) return props.name.replace(/_/g, '-');
|
||||
return null;
|
||||
});
|
||||
|
||||
return {
|
||||
sizeClass,
|
||||
customIconName,
|
||||
socialIconName,
|
||||
emitClick,
|
||||
};
|
||||
|
||||
@@ -182,6 +666,11 @@ body {
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
fill: currentColor;
|
||||
|
||||
&.svg-inline--fa {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-click {
|
||||
|
||||
@@ -69,3 +69,5 @@ export const MODULE_BAR_DEFAULT = [
|
||||
locked: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const AUTH_SSO_DRIVERS = ['oauth2', 'openid'];
|
||||
|
||||
@@ -305,7 +305,13 @@ export default defineComponent({
|
||||
];
|
||||
|
||||
const fieldsFiltered = computed(() => {
|
||||
return fields.value.filter((field: Field) => fieldsDenyList.includes(field.field) === false);
|
||||
return fields.value.filter((field: Field) => {
|
||||
// These fields should only be editable when creating new users
|
||||
if (!isNew.value && ['provider', 'external_identifier'].includes(field.field)) {
|
||||
field.meta.readonly = true;
|
||||
}
|
||||
return !fieldsDenyList.includes(field.field);
|
||||
});
|
||||
});
|
||||
|
||||
const { formFields } = useFormFields(fieldsFiltered);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { defineComponent, ref, onMounted } from 'vue';
|
||||
|
||||
import api from '@/api';
|
||||
import { hydrate } from '@/hydrate';
|
||||
@@ -37,6 +37,12 @@ export default defineComponent({
|
||||
|
||||
fetchUser();
|
||||
|
||||
onMounted(() => {
|
||||
if ('continue' in router.currentRoute.value.query) {
|
||||
hydrateAndLogin();
|
||||
}
|
||||
});
|
||||
|
||||
return { t, name, lastPage, loading, hydrateAndLogin };
|
||||
|
||||
async function fetchUser() {
|
||||
|
||||
@@ -17,19 +17,20 @@
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<sso-links />
|
||||
<sso-links :providers="providers" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, ref, computed, watch } from 'vue';
|
||||
import { defineComponent, ref, computed, watch, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import ssoLinks from '../sso-links.vue';
|
||||
import { login } from '@/auth';
|
||||
import { RequestError } from '@/api';
|
||||
import api, { RequestError } from '@/api';
|
||||
import { translateAPIError } from '@/lang';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
|
||||
type Credentials = {
|
||||
email: string;
|
||||
@@ -50,8 +51,11 @@ export default defineComponent({
|
||||
const error = ref<RequestError | string | null>(null);
|
||||
const otp = ref<string | null>(null);
|
||||
const requiresTFA = ref(false);
|
||||
const providers = ref([]);
|
||||
const userStore = useUserStore();
|
||||
|
||||
onMounted(() => fetchProviders());
|
||||
|
||||
watch(email, () => {
|
||||
if (requiresTFA.value === true) requiresTFA.value = false;
|
||||
});
|
||||
@@ -68,7 +72,28 @@ export default defineComponent({
|
||||
return null;
|
||||
});
|
||||
|
||||
return { t, errorFormatted, error, email, password, onSubmit, loggingIn, translateAPIError, otp, requiresTFA };
|
||||
return {
|
||||
t,
|
||||
errorFormatted,
|
||||
error,
|
||||
email,
|
||||
password,
|
||||
onSubmit,
|
||||
loggingIn,
|
||||
translateAPIError,
|
||||
otp,
|
||||
requiresTFA,
|
||||
providers,
|
||||
};
|
||||
|
||||
async function fetchProviders() {
|
||||
try {
|
||||
const response = await api.get('/auth');
|
||||
providers.value = response.data.data;
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (email.value === null || password.value === null) return;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<template>
|
||||
<div class="sso-links">
|
||||
<template v-if="providers && providers.length > 0">
|
||||
<template v-if="ssoProviders.length > 0">
|
||||
<v-divider />
|
||||
|
||||
<a v-for="provider in providers" :key="provider.name" class="sso-link" :href="provider.link">
|
||||
{{ t('log_in_with', { provider: provider.name }) }}
|
||||
<a v-for="provider in ssoProviders" :key="provider.name" class="sso-link" :href="provider.link">
|
||||
<div class="sso-icon">
|
||||
<v-icon :name="provider.icon" />
|
||||
</div>
|
||||
<div class="sso-title">
|
||||
{{ t('log_in_with', { provider: provider.name }) }}
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
@@ -12,40 +17,40 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, ref, onMounted } from 'vue';
|
||||
import api from '@/api';
|
||||
import { defineComponent, ref, watch, toRefs, PropType } from 'vue';
|
||||
import { AuthProvider } from '@/types';
|
||||
import { AUTH_SSO_DRIVERS } from '@/constants';
|
||||
import { getRootPath } from '@/utils/get-root-path';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import formatTitle from '@directus/format-title';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
props: {
|
||||
providers: {
|
||||
type: Array as PropType<AuthProvider[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const providers = ref([]);
|
||||
const loading = ref(false);
|
||||
const { providers } = toRefs(props);
|
||||
|
||||
onMounted(() => fetchProviders());
|
||||
const ssoProviders = ref<{ name: string; link: string; icon: string }[]>([]);
|
||||
|
||||
return { t, providers };
|
||||
watch(providers, () => {
|
||||
ssoProviders.value = providers.value
|
||||
.filter((provider: AuthProvider) => AUTH_SSO_DRIVERS.includes(provider.driver))
|
||||
.map((provider: AuthProvider) => ({
|
||||
name: formatTitle(provider.name),
|
||||
link: `${getRootPath()}auth/login/${provider.name}?redirect=${window.location.href.replace(
|
||||
location.search,
|
||||
'?continue'
|
||||
)}`,
|
||||
icon: provider.icon ?? 'account_circle',
|
||||
}));
|
||||
});
|
||||
|
||||
async function fetchProviders() {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get('/auth/oauth/');
|
||||
|
||||
providers.value = response.data.data?.map((providerName: string) => {
|
||||
return {
|
||||
name: providerName,
|
||||
link: `${getRootPath()}auth/oauth/${providerName.toLowerCase()}?redirect=${window.location.href}`,
|
||||
};
|
||||
});
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
return { t, ssoProviders };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -56,19 +61,39 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.sso-link {
|
||||
display: block;
|
||||
$sso-link-border-width: 2px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: var(--input-height);
|
||||
text-align: center;
|
||||
background-color: var(--background-normal);
|
||||
border: $sso-link-border-width var(--background-normal) solid;
|
||||
border-radius: var(--border-radius);
|
||||
transition: background var(--fast) var(--transition);
|
||||
transition: border-color var(--fast) var(--transition);
|
||||
|
||||
.sso-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--input-height);
|
||||
margin: -$sso-link-border-width;
|
||||
background-color: var(--background-normal-alt);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
span {
|
||||
--v-icon-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.sso-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px 0 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-normal-alt);
|
||||
border-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
& + & {
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './collections';
|
||||
export * from './error';
|
||||
export * from './insights';
|
||||
export * from './notifications';
|
||||
export * from './login';
|
||||
|
||||
5
app/src/types/login.ts
Normal file
5
app/src/types/login.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface AuthProvider {
|
||||
driver: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user