forked from FoundKeyGang/FoundKey
Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Johann150 | 3c9422dcb0 | |
Johann150 | d16c717c3b | |
Johann150 | 20d8f6cd36 | |
Johann150 | cb4a7f1981 | |
Johann150 | f64134c717 | |
Johann150 | f975f2d8aa | |
Johann150 | 929c0a895b |
|
@ -929,6 +929,10 @@ setTag: "Set tag"
|
|||
addTag: "Add tag"
|
||||
removeTag: "Remove tag"
|
||||
externalCssSnippets: "Some CSS snippets for your inspiration (not managed by FoundKey)"
|
||||
oauthErrorGoBack: "An error happened while trying to authenticate a 3rd party app.\
|
||||
\ Please go back and try again."
|
||||
appAuthorization: "App authorization"
|
||||
noPermissionsRequested: "(No permissions requested.)"
|
||||
_emailUnavailable:
|
||||
used: "This email address is already being used"
|
||||
format: "The format of this email address is invalid"
|
||||
|
@ -1247,38 +1251,37 @@ _2fa:
|
|||
\ authentication via hardware security keys that support FIDO2 to further secure\
|
||||
\ your account."
|
||||
_permissions:
|
||||
"read:account": "View your account information"
|
||||
"write:account": "Edit your account information"
|
||||
"read:blocks": "View your list of blocked users"
|
||||
"write:blocks": "Edit your list of blocked users"
|
||||
"read:drive": "Access your Drive files and folders"
|
||||
"write:drive": "Edit or delete your Drive files and folders"
|
||||
"read:favorites": "View your list of favorites"
|
||||
"write:favorites": "Edit your list of favorites"
|
||||
"read:following": "View information on who you follow"
|
||||
"write:following": "Follow or unfollow other accounts"
|
||||
"read:messaging": "View your chats"
|
||||
"write:messaging": "Compose or delete chat messages"
|
||||
"read:mutes": "View your list of muted users"
|
||||
"write:mutes": "Edit your list of muted users"
|
||||
"write:notes": "Compose or delete notes"
|
||||
"read:notifications": "View your notifications"
|
||||
"write:notifications": "Manage your notifications"
|
||||
"read:reactions": "View your reactions"
|
||||
"write:reactions": "Edit your reactions"
|
||||
"write:votes": "Vote on a poll"
|
||||
"read:pages": "View your pages"
|
||||
"write:pages": "Edit or delete your pages"
|
||||
"read:page-likes": "View your likes on pages"
|
||||
"write:page-likes": "Edit your likes on pages"
|
||||
"read:user-groups": "View your user groups"
|
||||
"write:user-groups": "Edit or delete your user groups"
|
||||
"read:channels": "View your channels"
|
||||
"write:channels": "Edit your channels"
|
||||
"read:gallery": "View your gallery"
|
||||
"write:gallery": "Edit your gallery"
|
||||
"read:gallery-likes": "View your list of liked gallery posts"
|
||||
"write:gallery-likes": "Edit your list of liked gallery posts"
|
||||
"read:account": "Read account information"
|
||||
"write:account": "Edit account information"
|
||||
"read:blocks": "Read which users are blocked"
|
||||
"write:blocks": "Block and unblock users"
|
||||
"read:drive": "List files and folders in the drive"
|
||||
"write:drive": "Create, change and delete files in the drive"
|
||||
"read:favorites": "List favourited notes"
|
||||
"write:favorites": "Favorite and unfavorite notes"
|
||||
"read:following": "List followed and following users"
|
||||
"write:following": "Follow and unfollow other users"
|
||||
"read:messaging": "View chat messages and history"
|
||||
"write:messaging": "Create and delete chat messages"
|
||||
"read:mutes": "List users which are muted or whose renotes are muted"
|
||||
"write:mutes": "Mute and unmute users or their renotes"
|
||||
"write:notes": "Create and delete notes"
|
||||
"read:notifications": "Read notifications"
|
||||
"write:notifications": "Mark notifications as read and create custom notifications"
|
||||
"write:reactions": "Create and delete reactions"
|
||||
"write:votes": "Vote in polls"
|
||||
"read:pages": "List and read pages"
|
||||
"write:pages": "Create, change and delete pages"
|
||||
"read:page-likes": "List and read page likes"
|
||||
"write:page-likes": "Like and unlike pages"
|
||||
"read:user-groups": "List and view joined, owned and invited to groups"
|
||||
"write:user-groups": "Create, modify, delete, transfer, join and leave groups. Invite and ban others from groups. Accept and reject group invitations."
|
||||
"read:channels": "List and read followed and joined channels"
|
||||
"write:channels": "Create, modify, follow and unfollow channels"
|
||||
"read:gallery": "List and read gallery posts"
|
||||
"write:gallery": "Create, modify and delete gallery posts"
|
||||
"read:gallery-likes": "List and read gallery post likes"
|
||||
"write:gallery-likes": "Like and unlike gallery posts"
|
||||
_auth:
|
||||
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
||||
shareAccessAsk: "Are you sure you want to authorize this application to access your\
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Bull from 'bull';
|
||||
import { In, LessThan } from 'typeorm';
|
||||
import { AttestationChallenges, Mutings, Signins } from '@/models/index.js';
|
||||
import { AttestationChallenges, AuthSessions, Mutings, Signins } from '@/models/index.js';
|
||||
import { publishUserEvent } from '@/services/stream.js';
|
||||
import { MINUTE, DAY } from '@/const.js';
|
||||
import { queueLogger } from '@/queue/logger.js';
|
||||
|
@ -35,7 +35,11 @@ export async function checkExpired(job: Bull.Job<Record<string, unknown>>, done:
|
|||
createdAt: LessThan(new Date(new Date().getTime() - 5 * MINUTE)),
|
||||
});
|
||||
|
||||
logger.succ('Deleted expired mutes, signins and attestation challenges.');
|
||||
await AuthSessions.delete({
|
||||
createdAt: LessThan(new Date(new Date().getTime() - 15 * MINUTE)),
|
||||
});
|
||||
|
||||
logger.succ('Deleted expired mutes, signins, attestation challenges and unused oauth authorization codes.');
|
||||
|
||||
done();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import Koa from 'koa';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import { Apps, AuthSessions, AccessTokens } from '@/models/index.js';
|
||||
import config from '@/config/index.js';
|
||||
|
||||
export async function oauth(ctx: Koa.Context): void {
|
||||
const {
|
||||
grant_type,
|
||||
code,
|
||||
// TODO: check redirect_uri
|
||||
// since this is also not checked in the legacy app authentication
|
||||
// it seems pointless to check it here, and it is also not stored.
|
||||
redirect_uri,
|
||||
} = ctx.request.body;
|
||||
|
||||
// check if any of the parameters are null or empty string
|
||||
if ([grant_type, code].some(x => !x)) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = {
|
||||
error: 'invalid_request',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (grant_type !== 'authorization_code') {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = {
|
||||
error: 'unsupported_grant_type',
|
||||
error_description: 'only authorization_code grants are supported',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const authHeader = ctx.headers.authorization;
|
||||
if (!authHeader?.toLowerCase().startsWith('basic ')) {
|
||||
ctx.response.status = 401;
|
||||
ctx.response.set('WWW-Authenticate', 'Basic');
|
||||
ctx.response.body = {
|
||||
error: 'invalid_client',
|
||||
error_description: 'HTTP Basic Authentication required',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const [client_id, client_secret] = new Buffer(authHeader.slice(6), 'base64')
|
||||
.toString('ascii')
|
||||
.split(':', 2);
|
||||
|
||||
const [app, session] = await Promise.all([
|
||||
Apps.findOneBy({
|
||||
id: client_id,
|
||||
secret: client_secret,
|
||||
}),
|
||||
AuthSessions.findOneBy({
|
||||
appId: client_id,
|
||||
token: code,
|
||||
// only check for approved auth sessions
|
||||
userId: Not(IsNull()),
|
||||
}),
|
||||
]);
|
||||
if (app == null) {
|
||||
ctx.response.status = 401;
|
||||
ctx.response.set('WWW-Authenticate', 'Basic');
|
||||
ctx.response.body = {
|
||||
error: 'invalid_client',
|
||||
error_description: 'authentication failed',
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (session == null) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = {
|
||||
error: 'invalid_grant',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const [ token ] = await Promise.all([
|
||||
AccessTokens.findOneByOrFail({
|
||||
appId: client_id,
|
||||
userId: session.userId,
|
||||
}),
|
||||
// session is single use
|
||||
AuthSessions.delete(session.id),
|
||||
]);
|
||||
|
||||
ctx.response.status = 200;
|
||||
ctx.response.body = {
|
||||
access_token: token.token,
|
||||
token_type: 'bearer',
|
||||
// FIXME: per-token permissions
|
||||
scope: app.permission.join(' '),
|
||||
};
|
||||
};
|
|
@ -66,6 +66,7 @@ import * as ep___ap_show from './endpoints/ap/show.js';
|
|||
import * as ep___app_create from './endpoints/app/create.js';
|
||||
import * as ep___app_show from './endpoints/app/show.js';
|
||||
import * as ep___auth_accept from './endpoints/auth/accept.js';
|
||||
import * as ep___auth_deny from './endpoints/auth/deny.js';
|
||||
import * as ep___auth_session_generate from './endpoints/auth/session/generate.js';
|
||||
import * as ep___auth_session_show from './endpoints/auth/session/show.js';
|
||||
import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js';
|
||||
|
@ -376,6 +377,7 @@ const eps = [
|
|||
['app/create', ep___app_create],
|
||||
['app/show', ep___app_show],
|
||||
['auth/accept', ep___auth_accept],
|
||||
['auth/deny', ep___auth_deny],
|
||||
['auth/session/generate', ep___auth_session_generate],
|
||||
['auth/session/show', ep___auth_session_show],
|
||||
['auth/session/userkey', ep___auth_session_userkey],
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { AuthSessions } from '@/models/index.js';
|
||||
import define from '../../define.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['auth'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
secure: true,
|
||||
|
||||
errors: {
|
||||
noSuchSession: {
|
||||
message: 'No such session.',
|
||||
code: 'NO_SUCH_SESSION',
|
||||
id: '9c72d8de-391a-43c1-9d06-08d29efde8df',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
token: { type: 'string' },
|
||||
},
|
||||
required: ['token'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const result = await AuthSessions.delete({
|
||||
token: ps.token,
|
||||
});
|
||||
|
||||
if (result.affected == 0) {
|
||||
throw new ApiError(meta.errors.noSuchSession);
|
||||
}
|
||||
});
|
|
@ -23,6 +23,19 @@ export const meta = {
|
|||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
// stuff that auth/session/show would respond with
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'The ID of the authentication session. Same as returned by `auth/session/show`.',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
app: {
|
||||
type: 'object',
|
||||
description: 'The App requesting permissions. Same as returned by `auth/session/show`.',
|
||||
optional: false, nullable: false,
|
||||
ref: 'App',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -36,17 +49,27 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
appSecret: { type: 'string' },
|
||||
},
|
||||
required: ['appSecret'],
|
||||
oneOf: [{
|
||||
type: 'object',
|
||||
properties: {
|
||||
clientId: { type: 'string' },
|
||||
},
|
||||
required: ['clientId']
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
appSecret: { type: 'string' },
|
||||
},
|
||||
required: ['appSecret'],
|
||||
}],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
// Lookup app
|
||||
const app = await Apps.findOneBy({
|
||||
const app = await Apps.findOneBy(ps.clientId ? {
|
||||
id: ps.clientId,
|
||||
} : {
|
||||
secret: ps.appSecret,
|
||||
});
|
||||
|
||||
|
@ -56,10 +79,11 @@ export default define(meta, paramDef, async (ps) => {
|
|||
|
||||
// Generate token
|
||||
const token = uuid();
|
||||
const id = genId();
|
||||
|
||||
// Create session token document
|
||||
const doc = await AuthSessions.insert({
|
||||
id: genId(),
|
||||
id,
|
||||
createdAt: new Date(),
|
||||
appId: app.id,
|
||||
token,
|
||||
|
@ -68,5 +92,7 @@ export default define(meta, paramDef, async (ps) => {
|
|||
return {
|
||||
token: doc.token,
|
||||
url: `${config.authUrl}/${doc.token}`,
|
||||
id,
|
||||
app: await Apps.pack(app),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
This route is already in use, but the functionality is provided
|
||||
by '@/server/api/common/oauth.ts'. The route is not here because
|
||||
that route requires more deep level access to HTTP data.
|
||||
*/
|
|
@ -15,6 +15,7 @@ import handler from './api-handler.js';
|
|||
import signup from './private/signup.js';
|
||||
import signin from './private/signin.js';
|
||||
import signupPending from './private/signup-pending.js';
|
||||
import { oauth } from './common/oauth.js';
|
||||
import discord from './service/discord.js';
|
||||
import github from './service/github.js';
|
||||
import twitter from './service/twitter.js';
|
||||
|
@ -33,8 +34,9 @@ app.use(async (ctx, next) => {
|
|||
});
|
||||
|
||||
app.use(bodyParser({
|
||||
// リクエストが multipart/form-data でない限りはJSONだと見なす
|
||||
detectJSON: ctx => !ctx.is('multipart/form-data'),
|
||||
// assume it is JSON unless it is multipart/form-data (for file uploads) or
|
||||
// application/x-www-form-urlencoded (for OAuth)
|
||||
detectJSON: ctx => !ctx.is('multipart/form-data') && !ctx.is('application/x-www-form-urlencoded'),
|
||||
}));
|
||||
|
||||
// Init multer instance
|
||||
|
@ -77,6 +79,9 @@ for (const endpoint of endpoints) {
|
|||
}
|
||||
}
|
||||
|
||||
// the OAuth endpoint does some shenanigans and can not use the normal API handler
|
||||
router.post('/auth/session/oauth', oauth);
|
||||
|
||||
router.post('/signup', signup);
|
||||
router.post('/signin', signin);
|
||||
router.post('/signup-pending', signupPending);
|
||||
|
|
|
@ -2,6 +2,10 @@ import config from '@/config/index.js';
|
|||
import endpoints from '../endpoints.js';
|
||||
import { errors as basicErrors } from './errors.js';
|
||||
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
||||
import { kinds } from '@/misc/api-permissions.js';
|
||||
import { I18n } from '@/misc/i18n.js';
|
||||
|
||||
const i18n = new I18n('en-US');
|
||||
|
||||
export function genOpenapiSpec() {
|
||||
const spec = {
|
||||
|
@ -33,10 +37,18 @@ export function genOpenapiSpec() {
|
|||
in: 'body',
|
||||
name: 'i',
|
||||
},
|
||||
// TODO: change this to oauth2 when the remaining oauth stuff is set up
|
||||
Bearer: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
OAuth: {
|
||||
type: 'oauth2',
|
||||
flows: {
|
||||
authorizationCode: {
|
||||
authorizationUrl: `${config.url}/auth`,
|
||||
tokenUrl: `${config.apiUrl}/auth/session/oauth`,
|
||||
scopes: kinds.reduce((acc, kind) => {
|
||||
acc[kind] = i18n.ts['_permissions'][kind];
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -80,10 +92,16 @@ export function genOpenapiSpec() {
|
|||
{
|
||||
ApiKeyAuth: [],
|
||||
},
|
||||
{
|
||||
Bearer: [],
|
||||
},
|
||||
];
|
||||
if (endpoint.meta.kind) {
|
||||
security.push({
|
||||
OAuth: [endpoint.meta.kind],
|
||||
});
|
||||
} else {
|
||||
security.push({
|
||||
OAuth: [],
|
||||
});
|
||||
}
|
||||
if (!endpoint.meta.requireCredential) {
|
||||
// add this to make authentication optional
|
||||
security.push({});
|
||||
|
|
|
@ -8,9 +8,12 @@
|
|||
</div>
|
||||
<div class="_content">
|
||||
<h2>{{ i18n.ts._auth.permissionAsk }}</h2>
|
||||
<ul>
|
||||
<ul v-if="app.permission.length > 0">
|
||||
<li v-for="p in app.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
||||
</ul>
|
||||
<template v-else>
|
||||
{{ i18n.ts.noPermissionRequested }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||
|
|
|
@ -1,29 +1,37 @@
|
|||
<template>
|
||||
<div v-if="$i">
|
||||
<MkLoading v-if="state == 'fetching'"/>
|
||||
<XForm
|
||||
v-else-if="state == 'waiting'"
|
||||
ref="form"
|
||||
class="form"
|
||||
:session="session"
|
||||
@denied="state = 'denied'"
|
||||
@accepted="accepted"
|
||||
/>
|
||||
<div v-else-if="state == 'denied'" class="denied">
|
||||
<h1>{{ i18n.ts._auth.denied }}</h1>
|
||||
</div>
|
||||
<div v-else-if="state == 'accepted'" class="accepted">
|
||||
<h1>{{ session.app.isAuthorized ? i18n.t('already-authorized') : i18n.ts.allowed }}</h1>
|
||||
<p v-if="session.app.callbackUrl">{{ i18n.ts._auth.callback }}<MkEllipsis/></p>
|
||||
<p v-if="!session.app.callbackUrl">{{ i18n.ts._auth.pleaseGoBack }}</p>
|
||||
</div>
|
||||
<div v-else-if="state == 'fetch-session-error'" class="error">
|
||||
<p>{{ i18n.ts.somethingHappened }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="signin">
|
||||
<MkSignin @login="onLogin"/>
|
||||
</div>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader/></template>
|
||||
<MkSpacer :max-content="700">
|
||||
<div v-if="$i">
|
||||
<MkLoading v-if="state == 'fetching'"/>
|
||||
<XForm
|
||||
v-else-if="state == 'waiting'"
|
||||
ref="form"
|
||||
class="form"
|
||||
:session="session"
|
||||
@denied="denied"
|
||||
@accepted="accepted"
|
||||
/>
|
||||
<div v-else-if="state == 'denied'" class="denied">
|
||||
<h1>{{ i18n.ts._auth.denied }}</h1>
|
||||
</div>
|
||||
<div v-else-if="state == 'accepted'" class="accepted">
|
||||
<h1>{{ session.app.isAuthorized ? i18n.t('already-authorized') : i18n.ts.allowed }}</h1>
|
||||
<p v-if="session.app.callbackUrl">{{ i18n.ts._auth.callback }}<MkEllipsis/></p>
|
||||
<p v-if="!session.app.callbackUrl">{{ i18n.ts._auth.pleaseGoBack }}</p>
|
||||
</div>
|
||||
<div v-else-if="state == 'fetch-session-error'" class="error">
|
||||
<p>{{ i18n.ts.somethingHappened }}</p>
|
||||
</div>
|
||||
<div v-else-if="state == 'oauth-error'" class="error">
|
||||
<p>{{ i18n.ts.oauthErrorGoBack }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="signin">
|
||||
<MkSignin @login="onLogin"/>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -33,48 +41,123 @@ import MkSignin from '@/components/signin.vue';
|
|||
import * as os from '@/os';
|
||||
import { login , $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { query, appendQuery } from '@/scripts/url';
|
||||
|
||||
const props = defineProps<{
|
||||
token: string;
|
||||
token?: string;
|
||||
}>();
|
||||
|
||||
let state: 'fetching' | 'waiting' | 'denied' | 'accepted' | 'fetch-session-error' = $ref('fetching');
|
||||
let state: 'fetching' | 'waiting' | 'denied' | 'accepted' | 'fetch-session-error' | 'oauth-error' = $ref('fetching');
|
||||
let session = $ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
// if this is an OAuth request, will contain the URLSearchParams
|
||||
let oauth = null;
|
||||
|
||||
onMounted(async () => {
|
||||
if (!$i) return;
|
||||
|
||||
// Fetch session
|
||||
os.api('auth/session/show', {
|
||||
token: props.token,
|
||||
}).then(fetchedSession => {
|
||||
session = fetchedSession;
|
||||
|
||||
// 既に連携していた場合
|
||||
if (session.app.isAuthorized) {
|
||||
os.api('auth/accept', {
|
||||
token: session.token,
|
||||
}).then(() => {
|
||||
this.accepted();
|
||||
});
|
||||
} else {
|
||||
state = 'waiting';
|
||||
// detect whether this is actual OAuth or "legacy" auth
|
||||
const query = new URLSearchParams(location.search);
|
||||
if (query.get('response_type') === 'code') {
|
||||
// OAuth request detected!
|
||||
oauth = query;
|
||||
// as a kind of hack, we first have to start the session for the OAuth client
|
||||
const clientId = query.get('client_id');
|
||||
if (!clientId) {
|
||||
state = 'fetch-session-error';
|
||||
return;
|
||||
}
|
||||
}).catch(() => {
|
||||
|
||||
session = await os.api('auth/session/generate', {
|
||||
clientId,
|
||||
}).catch(e => {
|
||||
const params = {
|
||||
error: 'server_error',
|
||||
};
|
||||
// try to determine the cause of the error
|
||||
if (e.code === 'NO_SUCH_APP') {
|
||||
params.error = 'invalid_request';
|
||||
params.error_description = 'unknown client_id';
|
||||
} else if (e.message) {
|
||||
params.error_description = e.message;
|
||||
}
|
||||
|
||||
if (oauth.has('state')) {
|
||||
params.state = oauth.get('state');
|
||||
}
|
||||
|
||||
location.href = appendQuery(session.app.callbackUrl, query(params));
|
||||
});
|
||||
} else if (!props.token) {
|
||||
state = 'fetch-session-error';
|
||||
});
|
||||
} else {
|
||||
session = await os.api('auth/session/show', {
|
||||
token: props.token,
|
||||
}).catch(() => {
|
||||
state = 'fetch-session-error';
|
||||
});
|
||||
}
|
||||
|
||||
// abort if an error occurred while trying to get the session info
|
||||
if (state === 'fetch-session-error') return;
|
||||
|
||||
// check whether the user already authorized the app earlier
|
||||
if (session.app.isAuthorized) {
|
||||
// already authorized, move on through!
|
||||
os.api('auth/accept', {
|
||||
token: session.token,
|
||||
}).then(() => {
|
||||
accepted();
|
||||
});
|
||||
} else {
|
||||
// user still has to give consent
|
||||
state = 'waiting';
|
||||
}
|
||||
});
|
||||
|
||||
function accepted(): void {
|
||||
state = 'accepted';
|
||||
if (session.app.callbackUrl) {
|
||||
if (oauth) {
|
||||
// redirect with authorization token
|
||||
const params = {
|
||||
code: session.token,
|
||||
};
|
||||
if (oauth.has('state')) {
|
||||
params.state = oauth.get('state');
|
||||
}
|
||||
|
||||
location.href = appendQuery(session.app.callbackUrl, query(params));
|
||||
} else if (session.app.callbackUrl) {
|
||||
// do whatever the legacy auth did
|
||||
location.href = appendQuery(session.app.callbackUrl, query({ token: session.token }));
|
||||
}
|
||||
}
|
||||
|
||||
function denied(): void {
|
||||
state = 'denied';
|
||||
if (oauth) {
|
||||
// redirect with error code
|
||||
const params = {
|
||||
error: 'access_denied',
|
||||
error_description: 'The user denied permission.',
|
||||
};
|
||||
if (oauth.has('state')) {
|
||||
params.state = oauth.get('state');
|
||||
}
|
||||
|
||||
location.href = appendQuery(session.app.callbackUrl, query(params));
|
||||
} else {
|
||||
// legacy auth didn't do anything in this case...
|
||||
}
|
||||
}
|
||||
|
||||
function onLogin(res): void {
|
||||
login(res.i);
|
||||
}
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.appAuthorization,
|
||||
icon: 'fas fa-shield',
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -98,6 +98,9 @@ export const routes = [{
|
|||
}, {
|
||||
path: '/preview',
|
||||
component: page(() => import('./pages/preview.vue')),
|
||||
}, {
|
||||
path: '/auth',
|
||||
component: page(() => import('./pages/auth.vue')),
|
||||
}, {
|
||||
path: '/auth/:token',
|
||||
component: page(() => import('./pages/auth.vue')),
|
||||
|
|
Loading…
Reference in New Issue