WIP: implement oauth2 oob #69
7 changed files with 2800 additions and 2716 deletions
|
@ -2257,6 +2257,7 @@ _auth:
|
||||||
scopeUser: "Operate as the following user"
|
scopeUser: "Operate as the following user"
|
||||||
pleaseLogin: "Please log in to authorize applications."
|
pleaseLogin: "Please log in to authorize applications."
|
||||||
byClickingYouWillBeRedirectedToThisUrl: "When access is granted, you will automatically be redirected to the following URL"
|
byClickingYouWillBeRedirectedToThisUrl: "When access is granted, you will automatically be redirected to the following URL"
|
||||||
|
pleasePassCodeToApplication: "Please copy this authorization code and paste it to the application."
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "All notes"
|
all: "All notes"
|
||||||
homeTimeline: "Notes from followed users"
|
homeTimeline: "Notes from followed users"
|
||||||
|
|
|
@ -53,7 +53,7 @@ function createMembers(record) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function generateDTS() {
|
export default function generateDTS() {
|
||||||
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
|
const locale = yaml.load(fs.readFileSync(`${__dirname}/en-US.yml`, 'utf-8'));
|
||||||
const members = createMembers(locale);
|
const members = createMembers(locale);
|
||||||
const elements = [
|
const elements = [
|
||||||
ts.factory.createVariableStatement(
|
ts.factory.createVariableStatement(
|
||||||
|
|
5358
locales/index.d.ts
vendored
5358
locales/index.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import dns from 'node:dns/promises';
|
import dns from 'node:dns/promises';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { request } from 'node:http';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import httpLinkHeader from 'http-link-header';
|
import httpLinkHeader from 'http-link-header';
|
||||||
|
@ -509,7 +510,14 @@ export class OAuth2ProviderService {
|
||||||
throw new AuthorizationError('Invalid redirect_uri', 'invalid_request');
|
throw new AuthorizationError('Invalid redirect_uri', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [null, clientInfo, redirectURI];
|
// Support OAuth2 OOB authentication. This is only supported for Mastodon
|
||||||
|
// clients, as OOB is insecure, however supporting is necessary for
|
||||||
|
// Mastodon compatibility.
|
||||||
|
let newRedirectURI = redirectURI;
|
||||||
|
if (mastodonAppId && redirectURI === 'urn:ietf:wg:oauth:2.0:oob') {
|
||||||
|
newRedirectURI = new URL('/oauth/oob', this.config.url).toString();
|
||||||
|
}
|
||||||
|
return [null, clientInfo, newRedirectURI];
|
||||||
})().then(args => done(...args), err => done(err));
|
})().then(args => done(...args), err => done(err));
|
||||||
}) as ValidateFunctionArity2));
|
}) as ValidateFunctionArity2));
|
||||||
fastify.use('/authorize', this.#server.errorHandler({
|
fastify.use('/authorize', this.#server.errorHandler({
|
||||||
|
|
89
packages/frontend/src/components/MkAuthOob.vue
Normal file
89
packages/frontend/src/components/MkAuthOob.vue
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.root" class="_gaps">
|
||||||
|
<div :class="$style.header" class="_gaps_s">
|
||||||
|
<template v-if="props.code">
|
||||||
|
<div :class="$style.iconFallback">
|
||||||
|
<i class="ti ti-check"></i>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.headerText">{{ i18n.ts._auth.accepted }}</div>
|
||||||
|
<div :class="$style.headerTextSub">{{ i18n.ts._auth.pleasePassCodeToApplication }}</div>
|
||||||
|
<div :class="$style.headerTextSub">{{ props.code }}</div>
|
||||||
|
<div class="_buttonsCenter">
|
||||||
|
<MkButton rounded @click="copy">{{ i18n.ts.copy }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="props.error">
|
||||||
|
<div :class="$style.iconFallback">
|
||||||
|
<i class="ti ti-x"></i>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.headerText">{{ props.error === 'access_denied' ? i18n.ts._auth.denied : props.error }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
code: string | null;
|
||||||
|
error: string | null;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function copy() {
|
||||||
|
copyToClipboard(props.code);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 48px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon,
|
||||||
|
.iconFallback {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--divider);
|
||||||
|
background-color: #fff;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconFallback {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--accentedBg);
|
||||||
|
color: var(--accent);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 54px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerText,
|
||||||
|
.headerTextSub {
|
||||||
|
text-align: center;
|
||||||
|
word-break: normal;
|
||||||
|
word-break: auto-phrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerText {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
</style>
|
53
packages/frontend/src/pages/oauth-oob.vue
Normal file
53
packages/frontend/src/pages/oauth-oob.vue
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MkAnimBg style="position: fixed; top: 0;"/>
|
||||||
|
<div :class="$style.formContainer">
|
||||||
|
<div :class="$style.form">
|
||||||
|
<MkAuthOob :code="code" :error="error"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import MkAnimBg from '@/components/MkAnimBg.vue';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import MkAuthOob from '@/components/MkAuthOob.vue';
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const code = params.get('code');
|
||||||
|
const error = params.get('error');
|
||||||
|
|
||||||
|
definePageMetadata(() => ({
|
||||||
|
title: 'OAuth',
|
||||||
|
icon: 'ti ti-apps',
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.formContainer {
|
||||||
|
min-height: 100svh;
|
||||||
|
padding: 32px 32px calc(env(safe-area-inset-bottom, 0px) + 32px) 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background-color: var(--panel);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: clip;
|
||||||
|
max-width: 500px;
|
||||||
|
width: calc(100vw - 64px);
|
||||||
|
height: min(65svh, calc(100svh - calc(env(safe-area-inset-bottom, 0px) + 64px)));
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -270,6 +270,9 @@ const routes: RouteDef[] = [{
|
||||||
}, {
|
}, {
|
||||||
path: '/oauth/authorize',
|
path: '/oauth/authorize',
|
||||||
component: page(() => import('@/pages/oauth.vue')),
|
component: page(() => import('@/pages/oauth.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/oauth/oob',
|
||||||
|
component: page(() => import('@/pages/oauth-oob.vue')),
|
||||||
}, {
|
}, {
|
||||||
path: '/sso/:kind/:serviceId',
|
path: '/sso/:kind/:serviceId',
|
||||||
component: page(() => import('@/pages/sso.vue')),
|
component: page(() => import('@/pages/sso.vue')),
|
||||||
|
|
Loading…
Reference in a new issue