1
0
Fork 0
mirror of https://github.com/Luzifer/webtotp.git synced 2024-12-22 20:31:21 +00:00

Initial version

This commit is contained in:
Knut Ahlers 2019-06-02 01:20:29 +02:00
commit 969859bd08
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
9 changed files with 6008 additions and 0 deletions

1
.dockerignore Symbolic link
View file

@ -0,0 +1 @@
.gitignore

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
dist/app.js
node_modules

24
Dockerfile Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

28
package.json Normal file
View 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
View 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
View 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
View 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'),
},
},
}