enhance(SSO): SAML認証でHTTP-POSTバインディングに対応 (MisskeyIO#531)
This commit is contained in:
parent
27c897d19f
commit
aebe9ae148
16 changed files with 185 additions and 107 deletions
|
|
@ -187,7 +187,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkInput v-model="service.name">
|
||||
<template #label>Name</template>
|
||||
</MkInput>
|
||||
<MkRadios v-model="service.type">
|
||||
<MkRadios v-model="service.type" :disabled="!!service.createdAt">
|
||||
<option value="jwt">JWT</option>
|
||||
<option value="saml">SAML</option>
|
||||
</MkRadios>
|
||||
|
|
@ -197,6 +197,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkTextarea v-model="service.audience">
|
||||
<template #label>Audience</template>
|
||||
</MkTextarea>
|
||||
<MkRadios v-model="service.binding">
|
||||
<option value="post">POST</option>
|
||||
<option value="redirect">Redirect</option>
|
||||
</MkRadios>
|
||||
<MkInput v-model="service.acsUrl">
|
||||
<template #label>Assertion Consumer Service URL</template>
|
||||
</MkInput>
|
||||
|
|
@ -426,6 +430,7 @@ function ssoServiceAddNew() {
|
|||
type: 'jwt',
|
||||
issuer: '',
|
||||
audience: '',
|
||||
binding: 'post',
|
||||
acsUrl: '',
|
||||
useCertificate: false,
|
||||
publicKey: '',
|
||||
|
|
@ -457,6 +462,7 @@ async function ssoServiceSave(service) {
|
|||
type: service.type,
|
||||
issuer: service.issuer,
|
||||
audience: service.audience.split('\n'),
|
||||
binding: service.binding,
|
||||
acsUrl: service.acsUrl,
|
||||
secret: service.publicKey,
|
||||
signatureAlgorithm: service.signatureAlgorithm,
|
||||
|
|
|
|||
|
|
@ -7,15 +7,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader/></template>
|
||||
<MkSpacer :contentMax="800">
|
||||
<div v-if="$i">
|
||||
<div v-if="$i && !loading">
|
||||
<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
|
||||
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
||||
<form :class="$style.buttons" :action="`/sso/${kind}/authorize`" accept-charset="utf-8" method="post">
|
||||
<input name="transaction_id" class="mk-input-tr-id-hidden" type="hidden" :value="transactionIdMeta?.content"/>
|
||||
<input name="login_token" class="mk-input-token-hidden" type="hidden" :value="$i.token"/>
|
||||
<MkButton inline name="cancel" value="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||
<MkButton inline primary>{{ i18n.ts.accept }}</MkButton>
|
||||
</form>
|
||||
<div :class="$style.buttons">
|
||||
<MkButton @click="onCancel">{{ i18n.ts.cancel }}</MkButton>
|
||||
<MkButton primary @click="onAccept">{{ i18n.ts.accept }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="$i && loading">
|
||||
<div>{{ i18n.ts._auth.callback }}</div>
|
||||
<MkLoading class="loading"/>
|
||||
<div style="display: none">
|
||||
<form ref="postBindingForm" method="post" :action="actionUrl" autocomplete="off">
|
||||
<input v-for="(value, key) in actionContext" :key="key" :name="key" :value="value" type="hidden"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p>
|
||||
|
|
@ -26,24 +33,63 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick } from 'vue';
|
||||
import MkSignin from '@/components/MkSignin.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { $i, login } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
const transactionIdMeta = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:transaction-id"]');
|
||||
if (transactionIdMeta) {
|
||||
transactionIdMeta.remove();
|
||||
}
|
||||
|
||||
const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:service-name"]')?.content;
|
||||
const kind = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:kind"]')?.content;
|
||||
|
||||
const loading = ref(false);
|
||||
const postBindingForm = ref<HTMLFormElement | null>(null);
|
||||
const actionUrl = ref<string | undefined>(undefined);
|
||||
const actionContext = ref<Record<string, string> | null>(null);
|
||||
|
||||
function onLogin(res): void {
|
||||
login(res.i);
|
||||
}
|
||||
|
||||
function onCancel(): void {
|
||||
if (history.length > 1) history.back();
|
||||
else location.href = '/';
|
||||
}
|
||||
|
||||
function onAccept(): void {
|
||||
loading.value = true;
|
||||
os.promiseDialog(authorize());
|
||||
}
|
||||
|
||||
async function authorize(): Promise<void> {
|
||||
const res = await fetch(`/sso/${kind}/authorize`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
transaction_id: transactionIdMeta?.content,
|
||||
login_token: $i!.token,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.binding === 'post') {
|
||||
actionUrl.value = json.action;
|
||||
actionContext.value = json.context;
|
||||
nextTick(() => {
|
||||
postBindingForm.value?.submit();
|
||||
});
|
||||
} else {
|
||||
location.href = json.action;
|
||||
}
|
||||
}
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: 'Single Sign-On',
|
||||
icon: 'ti ti-apps',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue