diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2018-03-27 11:47:17 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2018-03-27 11:47:17 +0900 |
| commit | 27183b2142e30a25975880012c00d203a9ebbf1a (patch) | |
| tree | fa39db23af4aed2cab709c1626c9b095038c69e9 /src | |
| parent | Refactor (diff) | |
| parent | Merge pull request #1304 from rinsuki/features/implement-media-video (diff) | |
| download | misskey-27183b2142e30a25975880012c00d203a9ebbf1a.tar.gz misskey-27183b2142e30a25975880012c00d203a9ebbf1a.tar.bz2 misskey-27183b2142e30a25975880012c00d203a9ebbf1a.zip | |
Merge branch 'master' of https://github.com/syuilo/misskey
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/models/user.ts | 2 | ||||
| -rw-r--r-- | src/api/private/signup.ts | 2 | ||||
| -rw-r--r-- | src/crypto_key.cc | 111 | ||||
| -rw-r--r-- | src/crypto_key.d.ts | 1 | ||||
| -rw-r--r-- | src/web/app/common/views/components/media-list.vue | 3 | ||||
| -rw-r--r-- | src/web/app/desktop/views/components/index.ts | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/components/media-video-dialog.vue | 70 | ||||
| -rw-r--r-- | src/web/app/desktop/views/components/media-video.vue | 67 | ||||
| -rw-r--r-- | src/web/app/mobile/views/components/index.ts | 2 | ||||
| -rw-r--r-- | src/web/app/mobile/views/components/media-video.vue | 36 |
10 files changed, 295 insertions, 1 deletions
diff --git a/src/api/models/user.ts b/src/api/models/user.ts index 3f0b10d4c2..d3875a65b9 100644 --- a/src/api/models/user.ts +++ b/src/api/models/user.ts @@ -59,6 +59,7 @@ export type IUser = { is_suspended: boolean; keywords: string[]; account: { + keypair: string; email: string; links: string[]; password: string; @@ -160,6 +161,7 @@ export const pack = ( delete _user.latest_post; // Remove private properties + delete _user.account.keypair; delete _user.account.password; delete _user.account.token; delete _user.account.two_factor_temp_secret; diff --git a/src/api/private/signup.ts b/src/api/private/signup.ts index 902642425c..690f3001cc 100644 --- a/src/api/private/signup.ts +++ b/src/api/private/signup.ts @@ -1,6 +1,7 @@ import * as uuid from 'uuid'; import * as express from 'express'; import * as bcrypt from 'bcryptjs'; +import { generate as generateKeypair } from '../../crypto_key'; import recaptcha = require('recaptcha-promise'); import User, { IUser, validateUsername, validatePassword, pack } from '../models/user'; import generateUserToken from '../common/generate-native-user-token'; @@ -119,6 +120,7 @@ export default async (req: express.Request, res: express.Response) => { username: username, username_lower: username.toLowerCase(), account: { + keypair: generateKeypair(), token: secret, email: null, links: null, diff --git a/src/crypto_key.cc b/src/crypto_key.cc new file mode 100644 index 0000000000..c8e4d8f7f0 --- /dev/null +++ b/src/crypto_key.cc @@ -0,0 +1,111 @@ +#include <nan.h> +#include <openssl/bio.h> +#include <openssl/buffer.h> +#include <openssl/crypto.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> + +NAN_METHOD(extractPublic) +{ + const auto sourceString = info[0]->ToString(); + if (!sourceString->IsOneByte()) { + Nan::ThrowError("Malformed character found"); + return; + } + + size_t sourceLength = sourceString->Length(); + const auto sourceBuf = new char[sourceLength]; + + Nan::DecodeWrite(sourceBuf, sourceLength, sourceString); + + const auto source = BIO_new_mem_buf(sourceBuf, sourceLength); + if (source == nullptr) { + Nan::ThrowError("Memory allocation failed"); + delete sourceBuf; + return; + } + + const auto rsa = PEM_read_bio_RSAPrivateKey(source, nullptr, nullptr, nullptr); + + BIO_free(source); + delete sourceBuf; + + if (rsa == nullptr) { + Nan::ThrowError("Decode failed"); + return; + } + + const auto destination = BIO_new(BIO_s_mem()); + if (destination == nullptr) { + Nan::ThrowError("Memory allocation failed"); + return; + } + + const auto result = PEM_write_bio_RSAPublicKey(destination, rsa); + + RSA_free(rsa); + + if (result != 1) { + Nan::ThrowError("Public key extraction failed"); + BIO_free(destination); + return; + } + + char *pem; + const auto pemLength = BIO_get_mem_data(destination, &pem); + + info.GetReturnValue().Set(Nan::Encode(pem, pemLength)); + BIO_free(destination); +} + +NAN_METHOD(generate) +{ + const auto exponent = BN_new(); + const auto mem = BIO_new(BIO_s_mem()); + const auto rsa = RSA_new(); + char *data; + long result; + + if (exponent == nullptr || mem == nullptr || rsa == nullptr) { + Nan::ThrowError("Memory allocation failed"); + goto done; + } + + result = BN_set_word(exponent, 65537); + if (result != 1) { + Nan::ThrowError("Exponent setting failed"); + goto done; + } + + result = RSA_generate_key_ex(rsa, 2048, exponent, nullptr); + if (result != 1) { + Nan::ThrowError("Key generation failed"); + goto done; + } + + result = PEM_write_bio_RSAPrivateKey(mem, rsa, NULL, NULL, 0, NULL, NULL); + if (result != 1) { + Nan::ThrowError("Key export failed"); + goto done; + } + + result = BIO_get_mem_data(mem, &data); + info.GetReturnValue().Set(Nan::Encode(data, result)); + +done: + RSA_free(rsa); + BIO_free(mem); + BN_free(exponent); +} + +NAN_MODULE_INIT(InitAll) +{ + Nan::Set(target, Nan::New<v8::String>("extractPublic").ToLocalChecked(), + Nan::GetFunction(Nan::New<v8::FunctionTemplate>(extractPublic)).ToLocalChecked()); + + Nan::Set(target, Nan::New<v8::String>("generate").ToLocalChecked(), + Nan::GetFunction(Nan::New<v8::FunctionTemplate>(generate)).ToLocalChecked()); +} + +NODE_MODULE(crypto_key, InitAll); diff --git a/src/crypto_key.d.ts b/src/crypto_key.d.ts new file mode 100644 index 0000000000..28ac2f9683 --- /dev/null +++ b/src/crypto_key.d.ts @@ -0,0 +1 @@ +export function generate(): String; diff --git a/src/web/app/common/views/components/media-list.vue b/src/web/app/common/views/components/media-list.vue index d0da584a40..64172ad0b4 100644 --- a/src/web/app/common/views/components/media-list.vue +++ b/src/web/app/common/views/components/media-list.vue @@ -1,7 +1,8 @@ <template> <div class="mk-media-list" :data-count="mediaList.length"> <template v-for="media in mediaList"> - <mk-media-image :image="media" :key="media.id"/> + <mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/> + <mk-media-image :image="media" :key="media.id" v-else /> </template> </div> </template> diff --git a/src/web/app/desktop/views/components/index.ts b/src/web/app/desktop/views/components/index.ts index 9bca603a53..3798bf6d2d 100644 --- a/src/web/app/desktop/views/components/index.ts +++ b/src/web/app/desktop/views/components/index.ts @@ -13,6 +13,7 @@ import analogClock from './analog-clock.vue'; import ellipsisIcon from './ellipsis-icon.vue'; import mediaImage from './media-image.vue'; import mediaImageDialog from './media-image-dialog.vue'; +import mediaVideo from './media-video.vue'; import notifications from './notifications.vue'; import postForm from './post-form.vue'; import repostForm from './repost-form.vue'; @@ -42,6 +43,7 @@ Vue.component('mk-analog-clock', analogClock); Vue.component('mk-ellipsis-icon', ellipsisIcon); Vue.component('mk-media-image', mediaImage); Vue.component('mk-media-image-dialog', mediaImageDialog); +Vue.component('mk-media-video', mediaVideo); Vue.component('mk-notifications', notifications); Vue.component('mk-post-form', postForm); Vue.component('mk-repost-form', repostForm); diff --git a/src/web/app/desktop/views/components/media-video-dialog.vue b/src/web/app/desktop/views/components/media-video-dialog.vue new file mode 100644 index 0000000000..cbf862cd1c --- /dev/null +++ b/src/web/app/desktop/views/components/media-video-dialog.vue @@ -0,0 +1,70 @@ +<template> +<div class="mk-media-video-dialog"> + <div class="bg" @click="close"></div> + <video :src="video.url" :title="video.name" controls autoplay ref="video"/> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import * as anime from 'animejs'; + +export default Vue.extend({ + props: ['video', 'start'], + mounted() { + anime({ + targets: this.$el, + opacity: 1, + duration: 100, + easing: 'linear' + }); + const videoTag = this.$refs.video as HTMLVideoElement + if (this.start) videoTag.currentTime = this.start + }, + methods: { + close() { + anime({ + targets: this.$el, + opacity: 0, + duration: 100, + easing: 'linear', + complete: () => this.$destroy() + }); + } + } +}); +</script> + +<style lang="stylus" scoped> +.mk-media-video-dialog + display block + position fixed + z-index 2048 + top 0 + left 0 + width 100% + height 100% + opacity 0 + + > .bg + display block + position fixed + z-index 1 + top 0 + left 0 + width 100% + height 100% + background rgba(0, 0, 0, 0.7) + + > video + position fixed + z-index 2 + top 0 + right 0 + bottom 0 + left 0 + max-width 80vw + max-height 80vh + margin auto + +</style> diff --git a/src/web/app/desktop/views/components/media-video.vue b/src/web/app/desktop/views/components/media-video.vue new file mode 100644 index 0000000000..4fd955a821 --- /dev/null +++ b/src/web/app/desktop/views/components/media-video.vue @@ -0,0 +1,67 @@ +<template> + <video class="mk-media-video" + :src="video.url" + :title="video.name" + controls + @dblclick.prevent="onClick" + ref="video" + v-if="inlinePlayable" /> + <a class="mk-media-video-thumbnail" + :href="video.url" + :style="imageStyle" + @click.prevent="onClick" + :title="video.name" + v-else> + %fa:R play-circle% + </a> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import MkMediaVideoDialog from './media-video-dialog.vue'; + +export default Vue.extend({ + props: ['video', 'inlinePlayable'], + computed: { + imageStyle(): any { + return { + 'background-image': `url(${this.video.url}?thumbnail&size=512)` + }; + } + }, + methods: { + onClick() { + const videoTag = this.$refs.video as (HTMLVideoElement | null) + var start = 0 + if (videoTag) { + start = videoTag.currentTime + videoTag.pause() + } + (this as any).os.new(MkMediaVideoDialog, { + video: this.video, + start, + }) + } + } +}) +</script> + +<style lang="stylus" scoped> +.mk-media-video + display block + width 100% + height 100% + border-radius 4px +.mk-media-video-thumbnail + display flex + justify-content center + align-items center + font-size 3.5em + + cursor zoom-in + overflow hidden + background-position center + background-size cover + width 100% + height 100% +</style> diff --git a/src/web/app/mobile/views/components/index.ts b/src/web/app/mobile/views/components/index.ts index 4743f50e0d..fb8f65f47d 100644 --- a/src/web/app/mobile/views/components/index.ts +++ b/src/web/app/mobile/views/components/index.ts @@ -5,6 +5,7 @@ import timeline from './timeline.vue'; import post from './post.vue'; import posts from './posts.vue'; import mediaImage from './media-image.vue'; +import mediaVideo from './media-video.vue'; import drive from './drive.vue'; import postPreview from './post-preview.vue'; import subPostContent from './sub-post-content.vue'; @@ -27,6 +28,7 @@ Vue.component('mk-timeline', timeline); Vue.component('mk-post', post); Vue.component('mk-posts', posts); Vue.component('mk-media-image', mediaImage); +Vue.component('mk-media-video', mediaVideo); Vue.component('mk-drive', drive); Vue.component('mk-post-preview', postPreview); Vue.component('mk-sub-post-content', subPostContent); diff --git a/src/web/app/mobile/views/components/media-video.vue b/src/web/app/mobile/views/components/media-video.vue new file mode 100644 index 0000000000..68cd48587a --- /dev/null +++ b/src/web/app/mobile/views/components/media-video.vue @@ -0,0 +1,36 @@ +<template> + <a class="mk-media-video" + :href="video.url" + target="_blank" + :style="imageStyle" + :title="video.name"> + %fa:R play-circle% + </a> +</template> + +<script lang="ts"> +import Vue from 'vue' +export default Vue.extend({ + props: ['video'], + computed: { + imageStyle(): any { + return { + 'background-image': `url(${this.video.url}?thumbnail&size=512)` + }; + } + },}) +</script> + +<style lang="stylus" scoped> +.mk-media-video + display flex + justify-content center + align-items center + + font-size 3.5em + overflow hidden + background-position center + background-size cover + width 100% + height 100% +</style> |