Merge branch 'master' of github.com:syuilo/misskey into swagger
This commit is contained in:
commit
0420fee5d2
26 changed files with 147 additions and 193 deletions
|
@ -1,6 +1,9 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "7.3.0"
|
||||
services:
|
||||
- mongodb
|
||||
- redis-server
|
||||
before_script:
|
||||
- "mkdir -p ./.config && cp ./.ci-files/config.yml ./.config"
|
||||
env:
|
||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,2 +1,11 @@
|
|||
# 1
|
||||
# Server
|
||||
## 1
|
||||
First version
|
||||
|
||||
# API
|
||||
## 2
|
||||
* パラメータ: _userkey --> i
|
||||
* トークンは、アクセストークン + アプリのシークレットキーをsha512したものに
|
||||
|
||||
## 1
|
||||
First version
|
||||
|
|
|
@ -70,5 +70,7 @@ block content
|
|||
| 次に、<code>#{api_url}/auth/session/userkey</code>へ<code>app_secret</code>としてApp Secretを、<code>token</code>としてセッションのトークンをパラメータとして付与したリクエストを送信してください。
|
||||
br
|
||||
| 上手くいけば、認証したユーザーのアクセストークンがレスポンスとして取得できます。おめでとうございます!
|
||||
p
|
||||
| 以降アクセストークンは、<strong>ユーザーのアクセストークン+アプリのシークレットキーをsha512したもの</strong>として扱います。
|
||||
|
||||
p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンを<code>_userkey</code>(「自分のアクセストークンを取得したい場合」の方法で取得したアクセストークンの場合は<code>i</code>)としてパラメータに含めるだけです。
|
||||
p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンを<code>i</code>としてパラメータに含めるだけです。
|
||||
|
|
|
@ -149,6 +149,7 @@ const aliasifyConfig = {
|
|||
'chart.js': './node_modules/chart.js/src/chart.js',
|
||||
'textarea-caret-position': './node_modules/textarea-caret/index.js',
|
||||
'misskey-text': './src/common/text/index.js',
|
||||
'nyaize': './node_modules/nyaize/built/index.js',
|
||||
'strength.js': './node_modules/syuilo-password-strength/strength.js',
|
||||
'cropper': './node_modules/cropperjs/dist/cropper.js',
|
||||
'Sortable': './node_modules/sortablejs/Sortable.js',
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"@types/browserify": "12.0.30",
|
||||
"@types/chalk": "0.4.31",
|
||||
"@types/compression": "0.0.33",
|
||||
"@types/cors": "0.0.33",
|
||||
"@types/cors": "2.8.0",
|
||||
"@types/elasticsearch": "5.0.9",
|
||||
"@types/escape-html": "0.0.19",
|
||||
"@types/event-stream": "3.3.30",
|
||||
|
@ -63,13 +63,14 @@
|
|||
"babel-preset-stage-3": "6.17.0",
|
||||
"bcrypt": "1.0.2",
|
||||
"body-parser": "1.15.2",
|
||||
"browserify": "13.1.1",
|
||||
"browserify": "13.3.0",
|
||||
"browserify-livescript": "0.2.3",
|
||||
"chalk": "1.1.3",
|
||||
"chart.js": "2.4.0",
|
||||
"compression": "1.6.2",
|
||||
"cors": "2.8.1",
|
||||
"cropperjs": "1.0.0-beta",
|
||||
"crypto": "0.0.3",
|
||||
"deepcopy": "0.6.3",
|
||||
"del": "2.2.2",
|
||||
"elasticsearch": "12.1.3",
|
||||
|
@ -99,10 +100,11 @@
|
|||
"livescript": "1.5.0",
|
||||
"mime-types": "2.1.13",
|
||||
"mocha": "3.2.0",
|
||||
"mongodb": "2.2.16",
|
||||
"mongodb": "2.2.19",
|
||||
"ms": "0.7.2",
|
||||
"multer": "1.2.1",
|
||||
"nprogress": "0.2.0",
|
||||
"nyaize": "0.0.2",
|
||||
"page": "1.7.1",
|
||||
"prominence": "0.2.0",
|
||||
"pug": "2.0.0-beta6",
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as express from 'express';
|
||||
import App from './models/app';
|
||||
import User from './models/user';
|
||||
import Userkey from './models/userkey';
|
||||
import AccessToken from './models/access-token';
|
||||
import isNativeToken from './common/is-native-token';
|
||||
|
||||
export interface IAuthContext {
|
||||
/**
|
||||
|
@ -20,10 +21,14 @@ export interface IAuthContext {
|
|||
isSecure: boolean;
|
||||
}
|
||||
|
||||
export default (req: express.Request) =>
|
||||
new Promise<IAuthContext>(async (resolve, reject) => {
|
||||
export default (req: express.Request) => new Promise<IAuthContext>(async (resolve, reject) => {
|
||||
const token = req.body['i'];
|
||||
if (token) {
|
||||
|
||||
if (token == null) {
|
||||
return resolve({ app: null, user: null, isSecure: false });
|
||||
}
|
||||
|
||||
if (isNativeToken(token)) {
|
||||
const user = await User
|
||||
.findOne({ token: token });
|
||||
|
||||
|
@ -36,26 +41,21 @@ export default (req: express.Request) =>
|
|||
user: user,
|
||||
isSecure: true
|
||||
});
|
||||
}
|
||||
|
||||
const userkey = req.headers['userkey'] || req.body['_userkey'];
|
||||
if (userkey) {
|
||||
const userkeyDoc = await Userkey.findOne({
|
||||
key: userkey
|
||||
} else {
|
||||
const accessToken = await AccessToken.findOne({
|
||||
hash: token
|
||||
});
|
||||
|
||||
if (userkeyDoc === null) {
|
||||
return reject('invalid userkey');
|
||||
if (accessToken === null) {
|
||||
return reject('invalid signature');
|
||||
}
|
||||
|
||||
const app = await App
|
||||
.findOne({ _id: userkeyDoc.app_id });
|
||||
.findOne({ _id: accessToken.app_id });
|
||||
|
||||
const user = await User
|
||||
.findOne({ _id: userkeyDoc.user_id });
|
||||
.findOne({ _id: accessToken.user_id });
|
||||
|
||||
return resolve({ app: app, user: user, isSecure: false });
|
||||
}
|
||||
|
||||
return resolve({ app: null, user: null, isSecure: false });
|
||||
});
|
||||
|
|
1
src/api/common/is-native-token.ts
Normal file
1
src/api/common/is-native-token.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export default (token: string) => token[0] == '!';
|
|
@ -4,8 +4,10 @@
|
|||
* Module dependencies
|
||||
*/
|
||||
import rndstr from 'rndstr';
|
||||
const crypto = require('crypto');
|
||||
import App from '../../models/app';
|
||||
import AuthSess from '../../models/auth-session';
|
||||
import Userkey from '../../models/userkey';
|
||||
import AccessToken from '../../models/access-token';
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
|
@ -41,35 +43,46 @@ module.exports = (params, user) =>
|
|||
new Promise(async (res, rej) =>
|
||||
{
|
||||
// Get 'token' parameter
|
||||
const token = params.token;
|
||||
if (token == null) {
|
||||
const sesstoken = params.token;
|
||||
if (sesstoken == null) {
|
||||
return rej('token is required');
|
||||
}
|
||||
|
||||
// Fetch token
|
||||
const session = await AuthSess
|
||||
.findOne({ token: token });
|
||||
.findOne({ token: sesstoken });
|
||||
|
||||
if (session === null) {
|
||||
return rej('session not found');
|
||||
}
|
||||
|
||||
// Generate userkey
|
||||
const key = rndstr('a-zA-Z0-9', 32);
|
||||
// Generate access token
|
||||
const token = rndstr('a-zA-Z0-9', 32);
|
||||
|
||||
// Fetch exist userkey
|
||||
const exist = await Userkey.findOne({
|
||||
// Fetch exist access token
|
||||
const exist = await AccessToken.findOne({
|
||||
app_id: session.app_id,
|
||||
user_id: user._id,
|
||||
});
|
||||
|
||||
if (exist === null) {
|
||||
// Insert userkey doc
|
||||
await Userkey.insert({
|
||||
// Lookup app
|
||||
const app = await App.findOne({
|
||||
app_id: session.app_id
|
||||
});
|
||||
|
||||
// Generate Hash
|
||||
const sha512 = crypto.createHash('sha512');
|
||||
sha512.update(token + app.secret);
|
||||
const hash = sha512.digest('hex');
|
||||
|
||||
// Insert access token doc
|
||||
await AccessToken.insert({
|
||||
created_at: new Date(),
|
||||
app_id: session.app_id,
|
||||
user_id: user._id,
|
||||
key: key
|
||||
token: token,
|
||||
hash: hash
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import App from '../../../models/app';
|
||||
import AuthSess from '../../../models/auth-session';
|
||||
import Userkey from '../../../models/userkey';
|
||||
import AccessToken from '../../../models/access-token';
|
||||
import serialize from '../../../serializers/user';
|
||||
|
||||
/**
|
||||
|
@ -89,8 +89,8 @@ module.exports = (params) =>
|
|||
return rej('this session is not allowed yet');
|
||||
}
|
||||
|
||||
// Lookup userkey
|
||||
const userkey = await Userkey.findOne({
|
||||
// Lookup access token
|
||||
const accessToken = await AccessToken.findOne({
|
||||
app_id: app._id,
|
||||
user_id: session.user_id
|
||||
});
|
||||
|
@ -102,7 +102,7 @@ module.exports = (params) =>
|
|||
|
||||
// Response
|
||||
res({
|
||||
userkey: userkey.key,
|
||||
access_token: accessToken.token,
|
||||
user: await serialize(session.user_id, null, {
|
||||
detail: true
|
||||
})
|
||||
|
|
|
@ -19,9 +19,19 @@ module.exports = (params, me) =>
|
|||
new Promise(async (res, rej) =>
|
||||
{
|
||||
// Get 'user_id' parameter
|
||||
const userId = params.user_id;
|
||||
if (userId === undefined || userId === null) {
|
||||
return rej('user_id is required');
|
||||
let userId = params.user_id;
|
||||
if (userId === undefined || userId === null || userId === '') {
|
||||
userId = null;
|
||||
}
|
||||
|
||||
// Get 'username' parameter
|
||||
let username = params.username;
|
||||
if (username === undefined || username === null || username === '') {
|
||||
username = null;
|
||||
}
|
||||
|
||||
if (userId === null && username === null) {
|
||||
return rej('user_id or username is required');
|
||||
}
|
||||
|
||||
// Get 'with_replies' parameter
|
||||
|
@ -62,9 +72,9 @@ module.exports = (params, me) =>
|
|||
}
|
||||
|
||||
// Lookup user
|
||||
const user = await User.findOne({
|
||||
_id: new mongo.ObjectID(userId)
|
||||
});
|
||||
const user = userId !== null
|
||||
? await User.findOne({ _id: new mongo.ObjectID(userId) })
|
||||
: await User.findOne({ username_lower: username.toLowerCase() });
|
||||
|
||||
if (user === null) {
|
||||
return rej('user not found');
|
||||
|
|
6
src/api/models/access-token.ts
Normal file
6
src/api/models/access-token.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const collection = global.db.collection('access_tokens');
|
||||
|
||||
collection.createIndex('token');
|
||||
collection.createIndex('hash');
|
||||
|
||||
export default collection;
|
|
@ -1,5 +0,0 @@
|
|||
const collection = global.db.collection('userkeys');
|
||||
|
||||
collection.createIndex('key');
|
||||
|
||||
export default collection;
|
|
@ -48,7 +48,7 @@ export default async (req: express.Request, res: express.Response) => {
|
|||
const hash = bcrypt.hashSync(password, salt);
|
||||
|
||||
// Generate secret
|
||||
const secret = rndstr('a-zA-Z0-9', 32);
|
||||
const secret = '!' + rndstr('a-zA-Z0-9', 32);
|
||||
|
||||
// Create account
|
||||
const inserted = await User.insert({
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as mongo from 'mongodb';
|
|||
import deepcopy = require('deepcopy');
|
||||
import App from '../models/app';
|
||||
import User from '../models/user';
|
||||
import Userkey from '../models/userkey';
|
||||
import AccessToken from '../models/access-token';
|
||||
|
||||
/**
|
||||
* Serialize an app
|
||||
|
@ -71,7 +71,7 @@ export default (
|
|||
|
||||
if (me) {
|
||||
// 既に連携しているか
|
||||
const exist = await Userkey.count({
|
||||
const exist = await AccessToken.count({
|
||||
app_id: _app.id,
|
||||
user_id: me,
|
||||
}, {
|
||||
|
|
|
@ -2,6 +2,8 @@ import * as http from 'http';
|
|||
import * as websocket from 'websocket';
|
||||
import * as redis from 'redis';
|
||||
import User from './models/user';
|
||||
import AccessToken from './models/access-token';
|
||||
import isNativeToken from './common/is-native-token';
|
||||
|
||||
import homeStream from './stream/home';
|
||||
import messagingStream from './stream/messaging';
|
||||
|
@ -17,7 +19,13 @@ module.exports = (server: http.Server) => {
|
|||
ws.on('request', async (request) => {
|
||||
const connection = request.accept();
|
||||
|
||||
const user = await authenticate(connection);
|
||||
const user = await authenticate(connection, request.resourceURL.query.i);
|
||||
|
||||
if (user == null) {
|
||||
connection.send('authentication-failed');
|
||||
connection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect to Redis
|
||||
const subscriber = redis.createClient(
|
||||
|
@ -41,29 +49,36 @@ module.exports = (server: http.Server) => {
|
|||
});
|
||||
};
|
||||
|
||||
function authenticate(connection: websocket.connection): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Listen first message
|
||||
connection.once('message', async (data) => {
|
||||
const msg = JSON.parse(data.utf8Data);
|
||||
|
||||
function authenticate(connection: websocket.connection, token: string): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (isNativeToken(token)) {
|
||||
// Fetch user
|
||||
// SELECT _id
|
||||
const user = await User
|
||||
.findOne({
|
||||
token: msg.i
|
||||
token: token
|
||||
}, {
|
||||
_id: true
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
connection.close();
|
||||
return;
|
||||
resolve(user);
|
||||
} else {
|
||||
const accessToken = await AccessToken.findOne({
|
||||
hash: token
|
||||
});
|
||||
|
||||
if (accessToken == null) {
|
||||
return reject('invalid signature');
|
||||
}
|
||||
|
||||
connection.send('authenticated');
|
||||
// Fetch user
|
||||
// SELECT _id
|
||||
const user = await User
|
||||
.findOne({ _id: accessToken.user_id }, {
|
||||
_id: true
|
||||
});
|
||||
|
||||
resolve(user);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ try {
|
|||
checkForUpdate();
|
||||
|
||||
// Get token from cookie
|
||||
const i = (document.cookie.match(/i=(\w+)/) || [null, null])[1];
|
||||
const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1];
|
||||
|
||||
// ユーザーをフェッチしてコールバックする
|
||||
module.exports = callback => {
|
||||
|
|
|
@ -3,7 +3,7 @@ module.exports = (date) ->
|
|||
|
||||
text =
|
||||
date.get-full-year! + \年 +
|
||||
date.get-month! + \月 +
|
||||
date.get-month! + 1 + \月 +
|
||||
date.get-date! + \日 +
|
||||
' ' +
|
||||
date.get-hours! + \時 +
|
||||
|
|
|
@ -9,7 +9,7 @@ class Connection
|
|||
@event = riot.observable!
|
||||
@me = me
|
||||
host = CONFIG.api.url.replace \http \ws
|
||||
@socket = new ReconnectingWebSocket "#{host}/messaging?otherparty=#{otherparty}"
|
||||
@socket = new ReconnectingWebSocket "#{host}/messaging?i=#{me.token}&otherparty=#{otherparty}"
|
||||
|
||||
@socket.add-event-listener \open @on-open
|
||||
@socket.add-event-listener \message @on-message
|
||||
|
|
|
@ -9,13 +9,12 @@ module.exports = (me) ~>
|
|||
state-ev = riot.observable!
|
||||
event = riot.observable!
|
||||
|
||||
socket = new ReconnectingWebSocket CONFIG.api.url.replace \http \ws
|
||||
host = CONFIG.api.url.replace \http \ws
|
||||
socket = new ReconnectingWebSocket "#{host}?i=#{me.token}"
|
||||
|
||||
socket.onopen = ~>
|
||||
state := \connected
|
||||
state-ev.trigger \connected
|
||||
socket.send JSON.stringify do
|
||||
i: me.token
|
||||
|
||||
socket.onclose = ~>
|
||||
state := \reconnecting
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const riot = require('riot');
|
||||
const nyaize = require('nyaize').default;
|
||||
|
||||
module.exports = function(tokens, shouldBreak, escape) {
|
||||
if (shouldBreak == null) {
|
||||
|
@ -34,10 +35,7 @@ module.exports = function(tokens, shouldBreak, escape) {
|
|||
}).join('');
|
||||
|
||||
if (me && me.data && me.data.nya) {
|
||||
text = text.replace(/な/g, 'にゃ')
|
||||
.replace(/ニャ/g, 'にゃ')
|
||||
.replace(/にゃでにゃで/g, 'なでなで')
|
||||
.replace(/ニャデニャデ/g, 'ナデナデ');
|
||||
text = nyaize(text);
|
||||
}
|
||||
|
||||
return text;
|
||||
|
|
|
@ -11,7 +11,7 @@ script.
|
|||
|
||||
@absolute =
|
||||
@time.get-full-year! + \年 +
|
||||
@time.get-month! + \月 +
|
||||
@time.get-month! + 1 + \月 +
|
||||
@time.get-date! + \日 +
|
||||
' ' +
|
||||
@time.get-hours! + \時 +
|
||||
|
|
|
@ -32,11 +32,6 @@ boot(me => {
|
|||
// Register mixins
|
||||
mixins(me);
|
||||
|
||||
// Debug
|
||||
if (me != null && me.data.debug) {
|
||||
riot.mount(document.body.appendChild(document.createElement('mk-log-window')));
|
||||
}
|
||||
|
||||
// Start routing
|
||||
route(me);
|
||||
});
|
||||
|
|
|
@ -99,5 +99,3 @@ require './tags/user-followers-window.tag'
|
|||
require './tags/list-user.tag'
|
||||
require './tags/ui-notification.tag'
|
||||
require './tags/signin-history.tag'
|
||||
require './tags/log.tag'
|
||||
require './tags/log-window.tag'
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
mk-log-window
|
||||
mk-window@window(width={ '600px' }, height={ '400px' })
|
||||
<yield to="header">
|
||||
i.fa.fa-terminal
|
||||
| Log
|
||||
</yield>
|
||||
<yield to="content">
|
||||
mk-log
|
||||
</yield>
|
||||
|
||||
style.
|
||||
> mk-window
|
||||
[data-yield='header']
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
script.
|
||||
@on \mount ~>
|
||||
@refs.window.on \closed ~>
|
||||
@unmount!
|
|
@ -1,62 +0,0 @@
|
|||
mk-log
|
||||
header
|
||||
button.follow(class={ following: following }, onclick={ follow }) Follow
|
||||
div.logs@logs
|
||||
code(each={ logs })
|
||||
span.date { date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() }
|
||||
span.message { message }
|
||||
|
||||
style.
|
||||
display block
|
||||
height 100%
|
||||
color #fff
|
||||
background #000
|
||||
|
||||
> header
|
||||
height 32px
|
||||
background #343a42
|
||||
|
||||
> button
|
||||
line-height 32px
|
||||
|
||||
> .follow
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
|
||||
&.following
|
||||
color #ff0
|
||||
|
||||
> .logs
|
||||
height calc(100% - 32px)
|
||||
overflow auto
|
||||
|
||||
> code
|
||||
display block
|
||||
padding 4px 8px
|
||||
|
||||
&:hover
|
||||
background rgba(#fff, 0.15)
|
||||
|
||||
> .date
|
||||
margin-right 8px
|
||||
opacity 0.5
|
||||
|
||||
script.
|
||||
@mixin \log
|
||||
|
||||
@following = true
|
||||
|
||||
@on \mount ~>
|
||||
@log-event.on \log @on-log
|
||||
|
||||
@on \unmount ~>
|
||||
@log-event.off \log @on-log
|
||||
|
||||
@follow = ~>
|
||||
@following = true
|
||||
|
||||
@on-log = ~>
|
||||
@update!
|
||||
if @following
|
||||
@refs.logs.scroll-top = @refs.logs.scroll-height
|
|
@ -3,11 +3,10 @@ mk-user-preview
|
|||
img.avatar(src={ user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.main
|
||||
header
|
||||
div.left
|
||||
a.name(href={ CONFIG.url + '/' + user.username })
|
||||
| { user.name }
|
||||
span.username
|
||||
| @{ user.username }
|
||||
a.name(href={ CONFIG.url + '/' + user.username })
|
||||
| { user.name }
|
||||
span.username
|
||||
| @{ user.username }
|
||||
div.body
|
||||
div.bio { user.bio }
|
||||
|
||||
|
@ -57,36 +56,26 @@ style.
|
|||
width calc(100% - 74px)
|
||||
|
||||
> header
|
||||
white-space nowrap
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-bottom 2px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
> .name
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #777
|
||||
font-size 1em
|
||||
font-weight 700
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
> .left
|
||||
float left
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .name
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #777
|
||||
font-size 1em
|
||||
font-weight 700
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .username
|
||||
text-align left
|
||||
margin 0 0 0 8px
|
||||
color #ccc
|
||||
> .username
|
||||
text-align left
|
||||
margin 0 0 0 8px
|
||||
color #ccc
|
||||
|
||||
> .body
|
||||
|
||||
|
|
Loading…
Reference in a new issue