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