enhance(SSO): SAML認証でHTTP-POSTバインディングに対応 (MisskeyIO#531)

This commit is contained in:
まっちゃとーにゅ 2024-03-17 20:58:53 +09:00
parent 27c897d19f
commit aebe9ae148
No known key found for this signature in database
GPG key ID: 6AFBBF529601C1DB
16 changed files with 185 additions and 107 deletions

View file

@ -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,

View file

@ -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',