mirror of
https://github.com/Luzifer/webtotp.git
synced 2024-12-22 12:21:20 +00:00
Initial version
This commit is contained in:
commit
969859bd08
9 changed files with 6008 additions and 0 deletions
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
.gitignore
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
dist/app.js
|
||||||
|
node_modules
|
24
Dockerfile
Normal file
24
Dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
FROM golang:alpine as go
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
&& apk add git \
|
||||||
|
&& go get -v github.com/Luzifer/gziphttp
|
||||||
|
|
||||||
|
|
||||||
|
FROM node:alpine as node
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
&& npm ci \
|
||||||
|
&& npm run build
|
||||||
|
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
COPY --from=go /go/bin/gziphttp /usr/local/bin/
|
||||||
|
COPY --from=node /src/dist /usr/local/share/webtotp
|
||||||
|
|
||||||
|
EXPOSE 3000/tcp
|
||||||
|
CMD ["gziphttp", "-d", "/usr/local/share/webtotp"]
|
18
dist/index.html
vendored
Normal file
18
dist/index.html
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
|
||||||
|
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>WebTOTP</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
5731
package-lock.json
generated
Normal file
5731
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
28
package.json
Normal file
28
package.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
|
"babel-loader": "^7.1.5",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
|
"bootstrap": "^4.3.1",
|
||||||
|
"bootstrap-vue": "^2.0.0-rc.22",
|
||||||
|
"bootswatch": "^4.3.1",
|
||||||
|
"css-loader": "^1.0.0",
|
||||||
|
"otp": "^0.1.3",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"style-loader": "^0.23.1",
|
||||||
|
"vue": "^2.6.10",
|
||||||
|
"vue-loader": "^15.7.0",
|
||||||
|
"vue-qr": "^2.1.0",
|
||||||
|
"vue-template-compiler": "^2.6.10",
|
||||||
|
"webpack": "^4.16.5",
|
||||||
|
"webpack-cli": "^3.1.0"
|
||||||
|
},
|
||||||
|
"main": "src/main.js",
|
||||||
|
"name": "webotp",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack -p"
|
||||||
|
},
|
||||||
|
"version": "0.0.0"
|
||||||
|
}
|
134
src/app.vue
Normal file
134
src/app.vue
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
|
||||||
|
<b-navbar toggleable="lg" type="dark" variant="primary">
|
||||||
|
<b-navbar-brand href="#" @click="blankSecret">WebTOTP</b-navbar-brand>
|
||||||
|
</b-navbar>
|
||||||
|
|
||||||
|
<b-container>
|
||||||
|
<b-row class="justify-content-center mt-5">
|
||||||
|
<b-col cols="12" md="6">
|
||||||
|
|
||||||
|
<b-card header="Your code currently is..." v-if="code">
|
||||||
|
<b-card-text class="text-center">
|
||||||
|
<b-input
|
||||||
|
class="code text-center"
|
||||||
|
@click="copy($event)"
|
||||||
|
readonly
|
||||||
|
ref="code"
|
||||||
|
title="click to copy"
|
||||||
|
:value="code"
|
||||||
|
v-b-tooltip:hover
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p class="mb-0"><a href="#" @click.prevent="showQR = !showQR">Toggle QR-Code</a></p>
|
||||||
|
|
||||||
|
<p class="my-5" v-if="showQR"><vue-qr :text="qrURL" :size="200" :dotScale="1.0" /></p>
|
||||||
|
</b-card-text>
|
||||||
|
|
||||||
|
<b-progress
|
||||||
|
class="justify-content-center"
|
||||||
|
:max="30000"
|
||||||
|
slot="footer"
|
||||||
|
:value="timeLeft"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</b-card>
|
||||||
|
|
||||||
|
<b-card header="Set secret..." v-else>
|
||||||
|
<b-form-group label="Secret (Base32 encoded or plain):">
|
||||||
|
<b-form-input v-model="secret" trim></b-form-input>
|
||||||
|
</b-form-group>
|
||||||
|
<div class="text-right">
|
||||||
|
<b-btn @click="updateSecret" variant="success">Get code!</b-btn>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import otp from 'otp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'app',
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
qrURL() {
|
||||||
|
return otp({ secret: this.secret }).totpURL
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
code: null,
|
||||||
|
secret: null,
|
||||||
|
showQR: false,
|
||||||
|
ticker: null,
|
||||||
|
timeLeft: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
blankSecret() {
|
||||||
|
if (this.ticker) {
|
||||||
|
window.clearTimeout(this.ticker)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.code = ''
|
||||||
|
this.secret = ''
|
||||||
|
this.showQR = false
|
||||||
|
window.location.hash = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
copy(evt) {
|
||||||
|
evt.target.select()
|
||||||
|
document.execCommand("copy")
|
||||||
|
},
|
||||||
|
|
||||||
|
hashUpdate() {
|
||||||
|
if (window.location.hash === '') {
|
||||||
|
this.blankSecret()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.secret = window.location.hash.substr(1)
|
||||||
|
this.updateCode()
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCode() {
|
||||||
|
if (this.secret) {
|
||||||
|
this.code = otp({ secret: this.secret }).totp()
|
||||||
|
this.timeLeft = 30000 - new Date().getTime() % 30000
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ticker = window.setTimeout(() => this.updateCode(), 250)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSecret() {
|
||||||
|
window.location.hash = this.secret
|
||||||
|
this.updateCode()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.hashUpdate()
|
||||||
|
window.onhashchange = () => this.hashUpdate()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.code {
|
||||||
|
background-color: transparent;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 2.5em;
|
||||||
|
letter-spacing: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
17
src/main.js
Normal file
17
src/main.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import BootstrapVue from 'bootstrap-vue'
|
||||||
|
import VueQr from 'vue-qr'
|
||||||
|
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css'
|
||||||
|
import 'bootswatch/dist/darkly/bootstrap.css'
|
||||||
|
|
||||||
|
import app from './app.vue'
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue)
|
||||||
|
Vue.use(VueQr)
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
components: { app },
|
||||||
|
render: c => c('app'),
|
||||||
|
})
|
53
webpack.config.js
Normal file
53
webpack.config.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
const path = require('path')
|
||||||
|
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/main.js',
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /(node_modules|bower_components)/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: ['transform-object-rest-spread'],
|
||||||
|
presets: [
|
||||||
|
['env', { "targets": { "browsers": [">0.25%", "not ie 11", "not op_mini all"] } }]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
|
||||||
|
{ test: /\.vue$/, loader: 'vue-loader' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
minimize: true
|
||||||
|
},
|
||||||
|
|
||||||
|
output: {
|
||||||
|
filename: 'app.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new VueLoaderPlugin(),
|
||||||
|
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env': {
|
||||||
|
'NODE_ENV': JSON.stringify('production')
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.webpack.js', '.web.js', '.js'],
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
Loading…
Reference in a new issue