diff --git a/.travis.yml b/.travis.yml
index ee6cc0986..fb24086b7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4da46cf7..b97f12f2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,2 +1,11 @@
-# 1
+# Server
+## 1
+First version
+
+# API
+## 2
+* パラメータ: _userkey --> i
+* トークンは、アクセストークン + アプリのシークレットキーをsha512したものに
+
+## 1
First version
diff --git a/docs/api/getting-started.pug b/docs/api/getting-started.pug
index 8e6e6b4f4..e255a5e93 100644
--- a/docs/api/getting-started.pug
+++ b/docs/api/getting-started.pug
@@ -70,5 +70,7 @@ block content
| 次に、#{api_url}/auth/session/userkey
へapp_secret
としてApp Secretを、token
としてセッションのトークンをパラメータとして付与したリクエストを送信してください。
br
| 上手くいけば、認証したユーザーのアクセストークンがレスポンスとして取得できます。おめでとうございます!
+ p
+ | 以降アクセストークンは、ユーザーのアクセストークン+アプリのシークレットキーをsha512したものとして扱います。
- p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンを_userkey
(「自分のアクセストークンを取得したい場合」の方法で取得したアクセストークンの場合はi
)としてパラメータに含めるだけです。
+ p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンをi
としてパラメータに含めるだけです。
diff --git a/gulpfile.ts b/gulpfile.ts
index 1c5516801..5ae2652c6 100644
--- a/gulpfile.ts
+++ b/gulpfile.ts
@@ -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',
diff --git a/package.json b/package.json
index e06b1dff4..ea5704081 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts
index 5798adb83..50a55e51b 100644
--- a/src/api/authenticate.ts
+++ b/src/api/authenticate.ts
@@ -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(async (resolve, reject) => {
+export default (req: express.Request) => new Promise(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 });
});
diff --git a/src/api/common/is-native-token.ts b/src/api/common/is-native-token.ts
new file mode 100644
index 000000000..0769a4812
--- /dev/null
+++ b/src/api/common/is-native-token.ts
@@ -0,0 +1 @@
+export default (token: string) => token[0] == '!';
diff --git a/src/api/endpoints/auth/accept.js b/src/api/endpoints/auth/accept.js
index e584513c0..d60d95aea 100644
--- a/src/api/endpoints/auth/accept.js
+++ b/src/api/endpoints/auth/accept.js
@@ -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
});
}
diff --git a/src/api/endpoints/auth/session/userkey.js b/src/api/endpoints/auth/session/userkey.js
index 73fa643c9..9252046e5 100644
--- a/src/api/endpoints/auth/session/userkey.js
+++ b/src/api/endpoints/auth/session/userkey.js
@@ -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
})
diff --git a/src/api/endpoints/users/posts.js b/src/api/endpoints/users/posts.js
index 6d6f8a690..1b8dfe031 100644
--- a/src/api/endpoints/users/posts.js
+++ b/src/api/endpoints/users/posts.js
@@ -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');
diff --git a/src/api/models/access-token.ts b/src/api/models/access-token.ts
new file mode 100644
index 000000000..f94df954d
--- /dev/null
+++ b/src/api/models/access-token.ts
@@ -0,0 +1,6 @@
+const collection = global.db.collection('access_tokens');
+
+collection.createIndex('token');
+collection.createIndex('hash');
+
+export default collection;
diff --git a/src/api/models/userkey.ts b/src/api/models/userkey.ts
deleted file mode 100644
index 204f283a2..000000000
--- a/src/api/models/userkey.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-const collection = global.db.collection('userkeys');
-
-collection.createIndex('key');
-
-export default collection;
diff --git a/src/api/private/signup.ts b/src/api/private/signup.ts
index c50b07005..592dfcceb 100644
--- a/src/api/private/signup.ts
+++ b/src/api/private/signup.ts
@@ -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({
diff --git a/src/api/serializers/app.ts b/src/api/serializers/app.ts
index 1c4b244a3..b60bcb97e 100644
--- a/src/api/serializers/app.ts
+++ b/src/api/serializers/app.ts
@@ -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,
}, {
diff --git a/src/api/streaming.ts b/src/api/streaming.ts
index 38068d1e3..7a8d2d435 100644
--- a/src/api/streaming.ts
+++ b/src/api/streaming.ts
@@ -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 {
- 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 {
+ 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);
- });
+ }
});
}
diff --git a/src/web/app/boot.js b/src/web/app/boot.js
index 5067600c6..e8e504c2b 100644
--- a/src/web/app/boot.js
+++ b/src/web/app/boot.js
@@ -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 => {
diff --git a/src/web/app/common/scripts/date-stringify.ls b/src/web/app/common/scripts/date-stringify.ls
index 9aa8b3e6c..7e85192ce 100644
--- a/src/web/app/common/scripts/date-stringify.ls
+++ b/src/web/app/common/scripts/date-stringify.ls
@@ -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! + \時 +
diff --git a/src/web/app/common/scripts/messaging-stream.ls b/src/web/app/common/scripts/messaging-stream.ls
index 298285dc9..ac3e74f1f 100644
--- a/src/web/app/common/scripts/messaging-stream.ls
+++ b/src/web/app/common/scripts/messaging-stream.ls
@@ -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
diff --git a/src/web/app/common/scripts/stream.ls b/src/web/app/common/scripts/stream.ls
index 534048248..64ae03817 100644
--- a/src/web/app/common/scripts/stream.ls
+++ b/src/web/app/common/scripts/stream.ls
@@ -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
diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.js
index f6c531c6c..8ea2361b8 100644
--- a/src/web/app/common/scripts/text-compiler.js
+++ b/src/web/app/common/scripts/text-compiler.js
@@ -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;
diff --git a/src/web/app/common/tags/time.tag b/src/web/app/common/tags/time.tag
index 56c3b8ecc..52ad89a44 100644
--- a/src/web/app/common/tags/time.tag
+++ b/src/web/app/common/tags/time.tag
@@ -11,7 +11,7 @@ script.
@absolute =
@time.get-full-year! + \年 +
- @time.get-month! + \月 +
+ @time.get-month! + 1 + \月 +
@time.get-date! + \日 +
' ' +
@time.get-hours! + \時 +
diff --git a/src/web/app/desktop/script.js b/src/web/app/desktop/script.js
index 179cfa332..980ae9772 100644
--- a/src/web/app/desktop/script.js
+++ b/src/web/app/desktop/script.js
@@ -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);
});
diff --git a/src/web/app/desktop/tags.ls b/src/web/app/desktop/tags.ls
index f78d36734..90e888ee9 100644
--- a/src/web/app/desktop/tags.ls
+++ b/src/web/app/desktop/tags.ls
@@ -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'
diff --git a/src/web/app/desktop/tags/log-window.tag b/src/web/app/desktop/tags/log-window.tag
deleted file mode 100644
index 6dabc4de3..000000000
--- a/src/web/app/desktop/tags/log-window.tag
+++ /dev/null
@@ -1,20 +0,0 @@
-mk-log-window
- mk-window@window(width={ '600px' }, height={ '400px' })
-
- i.fa.fa-terminal
- | Log
-
-
- mk-log
-
-
-style.
- > mk-window
- [data-yield='header']
- > i
- margin-right 4px
-
-script.
- @on \mount ~>
- @refs.window.on \closed ~>
- @unmount!
diff --git a/src/web/app/desktop/tags/log.tag b/src/web/app/desktop/tags/log.tag
deleted file mode 100644
index 20e5f8f69..000000000
--- a/src/web/app/desktop/tags/log.tag
+++ /dev/null
@@ -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
diff --git a/src/web/app/mobile/tags/user-preview.tag b/src/web/app/mobile/tags/user-preview.tag
index 4f5fbc152..56bd93825 100644
--- a/src/web/app/mobile/tags/user-preview.tag
+++ b/src/web/app/mobile/tags/user-preview.tag
@@ -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