WIP: implement oauth2 oob #69

Draft
sugar wants to merge 1 commit from sugar/forkey:implement-oauth-oob into main
7 changed files with 2800 additions and 2716 deletions

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -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({

View 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>

View 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>

View file

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