QR-Codes can now be displayed after scanning.

- 2FA also supported
This commit is contained in:
2020-07-09 16:18:49 +02:00
parent a6a9d95042
commit 8865e9a394
17 changed files with 532 additions and 62 deletions

View File

@@ -12,11 +12,13 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ViewActivity"></activity>
<activity android:name=".PassphraseActivity" />
<activity
android:name=".CreateActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="portrait" />
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,48 +1,97 @@
package com.github.mondei1.offpass
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.ColorFilter
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.print.PrintAttributes
import android.print.PrintJob
import android.print.PrintManager
import android.util.Base64
import android.util.Log
import android.view.View
import android.view.animation.LinearInterpolator
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.google.zxing.BarcodeFormat
import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult
import com.journeyapps.barcodescanner.BarcodeEncoder
import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
import kotlinx.android.synthetic.main.activity_create.*
import kotlinx.coroutines.*
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.io.ByteArrayOutputStream
import java.util.*
class CreateActivity : AppCompatActivity() {
private var fragment_title: TextInput? = null
private var fragment_username: TextInput? = null
private var schema: QRSchema? = null
private var qrCodeBitmap: Bitmap? = null
private var fa_coroutine: Job? = null
private var mWebView: WebView? = null
private var printJob: PrintJob? = null
fun doPrint() {
val webView = WebView(this)
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, requesst: WebResourceRequest) = false
override fun onPageFinished(view: WebView?, url: String?) {
Log.i("Create Activity", "Template page finished loading")
createWebPrintJob(webView)
mWebView = null
}
}
var htmlDocument = String(this.resources.openRawResource(
this.resources.getIdentifier("print", "raw", this.packageName)
).readBytes()).replace("\n", "")
// Prepare html document
var byteArrayOutputStream = ByteArrayOutputStream()
this.qrCodeBitmap?.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
htmlDocument = htmlDocument.replace("\$DATE", Date().time.toString())
.replace("\$HINT", "Not configurable yet!")
.replace("\$QRCODE", Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP))
Log.i("Create Activity", htmlDocument)
webView.loadDataWithBaseURL("file:///android_asset/", htmlDocument, "text/HTML", "UTF-8", null)
mWebView = webView
}
fun createWebPrintJob(webView: WebView) {
(this.getSystemService(Context.PRINT_SERVICE) as? PrintManager)?.let { printManager ->
val jobName = "Offpass Document"
val printAdapter = webView.createPrintDocumentAdapter(jobName)
printManager.print(
jobName,
printAdapter,
PrintAttributes.Builder().build()
).also { printJob ->
this.printJob = printJob
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//this.schema = QRSchema(this)
//this.schema!!.decrypted_raw = "%JtuB4O9M42%Gitea|Nicolas|542superGoOD_pW&|klier.nicolas@protonmail.com|\$ul|(\$vb)\$O4|()What's your favorite series%Rick and morty|(2fa)otpauth://totp/OffPass%20Test?secret=d34gfkki5dkd5knifysrpgndd5xb2c7eddwki7ya4pvoisfa5c3ko5pv&issuer=Nicolas%20Klier"
//this.schema!!.parse(this)
this.schema = QRSchema(this)
this.schema!!.decrypted_raw = "%JtuB4O9M42%Gitea|Nicolas|542superGoOD_pW&|klier.nicolas@protonmail.com|\$ul|(\$vb)\$O4|()What's your favorite series%Rick and morty|(2fa)otpauth://totp/OffPass%20Test?secret=d34gfkki5dkd5knifysrpgndd5xb2c7eddwki7ya4pvoisfa5c3ko5pv&issuer=Nicolas%20Klier"
this.schema!!.parse(this)
//this.schema!!.build(arrayOf("website_url", "2fa", "What's your favorite series"), "123")
//this.schema!!.decrypt(this.schema!!.raw, "123")
this.schema!!.build(arrayOf("website_url", "2fa", "What's your favorite series"), "123")
this.schema!!.decrypt(this.schema!!.raw, "123")
setSupportActionBar(findViewById(R.id.toolbar))
@@ -51,6 +100,20 @@ class CreateActivity : AppCompatActivity() {
Log.i("CREATE", "Back got clicked!")
finish()
}
print_button.setOnClickListener {
// Set up printing
this.schema = QRSchema(this)
this.schema!!.title = title_input.text.toString()
this.schema!!.username = username_input.text.toString()
this.schema!!.password = password_input.text.toString()
this.schema!!.email = email_input.text.toString()
this.schema!!.website_url = url_input.text.toString()
this.schema!!.build(arrayOf(), "123")
val barcodeEncoder: BarcodeEncoder = BarcodeEncoder()
this.qrCodeBitmap = barcodeEncoder.encodeBitmap(this.schema!!.raw, BarcodeFormat.QR_CODE, 400, 400)
doPrint()
}
fa_input.isFocusable = false

View File

@@ -117,7 +117,7 @@ class CryptoOperations {
// Performing actual crypto operation
cipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(iv))
val ciphered = cipher.doFinal(encrypted_raw)
val ciphered = cipher.doFinal(encrypted_raw) // TODO: Catch error if decryption goes wrong
// Concat everything into one byte array
val byteBuffer: ByteBuffer = ByteBuffer.allocate(ciphered.size)

View File

@@ -2,7 +2,6 @@ package com.github.mondei1.offpass
import android.content.Intent
import android.os.Bundle
import android.os.HandlerThread
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
@@ -61,8 +60,11 @@ class MainActivity : AppCompatActivity() {
if (result.contents == null) {
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show()
} else {
Log.i("SCANNER", "Scanned: " + result.contents)
Toast.makeText(this, "Scanned: " + result.contents, Toast.LENGTH_LONG).show()
// TODO: Check if scanned QR-Code is an OffPass one.
val intent: Intent = Intent(this, PassphraseActivity::class.java)
intent.putExtra("raw", result.contents)
startActivity(intent)
//Toast.makeText(this, "Scanned: " + result.contents, Toast.LENGTH_LONG).show()
}
}

View File

@@ -0,0 +1,34 @@
package com.github.mondei1.offpass
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import kotlinx.android.synthetic.main.activity_passphrase.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
class PassphraseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_passphrase)
val raw: String = intent.getStringExtra("raw")!!
val qrSchema: QRSchema = QRSchema(this)
back.setOnClickListener {
finish()
}
decrypt_button.setOnClickListener {
decrypt_button.text = "Decrypting ..."
runBlocking {
qrSchema.decrypt(raw, passphrase_input.text.toString())
}
val intent: Intent = Intent(this, ViewActivity::class.java)
intent.putExtra("decrypted_raw",qrSchema.decrypted_raw)
startActivity(intent)
}
}
}

View File

@@ -18,7 +18,7 @@ class QRSchema {
var decrypted_raw: String = ""
// Parsed content
lateinit var session_key: String
var session_key: String = ""
lateinit var title: String
lateinit var username: String
lateinit var password: String
@@ -100,23 +100,23 @@ class QRSchema {
var key = fields[i].substring(1, closingBracket)
var value = fields[i].substring(closingBracket+1, fields[i].length)
// Check if key and or value are compressed
if (key.startsWith("$")) {
runBlocking {
key = resolve(key, context)
}
}
if (value.startsWith("$")) {
runBlocking {
value = resolve(value, context)
}
}
// We got a security question/awnser
if (key == "") {
val qa = value.split("%")
question_awnser.put(qa[0], qa[1]);
} else {
// Check if key and or value are compressed
if (key.startsWith("$")) {
runBlocking {
key = resolve(key, context)
}
}
if (value.startsWith("$")) {
runBlocking {
value = resolve(value, context)
}
}
custom.put(key, value)
}
} else {

View File

@@ -0,0 +1,77 @@
package com.github.mondei1.offpass
import android.R.attr.key
import android.annotation.SuppressLint
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
import kotlinx.android.synthetic.main.activity_view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class ViewActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view)
val qrSchema: QRSchema = QRSchema(this)
back.setOnClickListener {
val i = Intent(this, MainActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(i)
}
qrSchema.decrypted_raw = intent.getStringExtra("decrypted_raw")!!
qrSchema.parse(this)
title_label.text = qrSchema.title
username_input.setText(qrSchema.username, TextView.BufferType.EDITABLE)
password_input.setText(qrSchema.password, TextView.BufferType.EDITABLE)
email_input.setText(qrSchema.email, TextView.BufferType.EDITABLE)
url_input.setText(qrSchema.website_url, TextView.BufferType.EDITABLE)
if (qrSchema.custom.containsKey("2fa")) {
val fa_uri = Uri.parse(qrSchema.custom.get("2fa"))
val fa_generator = GoogleAuthenticator(base32secret = fa_uri.getQueryParameter("secret").toString())
fa_label.text = "${fa_uri.path?.replace("/", "")} (${fa_uri.getQueryParameter("issuer")})"
fa_progress.visibility = View.VISIBLE
GlobalScope.launch(Dispatchers.Main) {
while (true) {
fa_input.setText(fa_generator.generate())
var seconds_remaining = CryptoOperations().getRemainingTOTPTime()
Log.i("2FA Generator", "Remaining: $seconds_remaining")
if (seconds_remaining == 0) seconds_remaining = 30
// Change color
if (seconds_remaining >= 15) {
fa_progress.progressTintList = ColorStateList.valueOf(Color.GREEN)
} else if (seconds_remaining < 15 && seconds_remaining >= 8) {
fa_progress.progressTintList = ColorStateList.valueOf(Color.YELLOW)
} else {
fa_progress.progressTintList = ColorStateList.valueOf(Color.RED)
}
fa_progress.progress = seconds_remaining
delay(1000)
}
}
} else {
fa_layout.visibility = View.GONE
}
}
}

View File

@@ -14,19 +14,19 @@
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="Create">
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/textView4"
android:id="@+id/print_button"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="86dp"
android:layout_height="50dp"
android:layout_margin="0dp"
android:layout_marginStart="64dp"
android:backgroundTint="#F8F8F8"
android:padding="0dp"
android:src="@drawable/printer"

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
tools:context=".PassphraseActivity">
<EditText
android:id="@+id/passphrase_input"
android:layout_width="234dp"
android:layout_height="wrap_content"
android:layout_marginStart="88dp"
android:layout_marginEnd="89dp"
android:backgroundTint="#757575"
android:ems="10"
android:hint="Passphrase"
android:includeFontPadding="false"
android:inputType="textPassword"
android:letterSpacing="0.2"
android:paddingBottom="15dp"
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textColorHint="#757575"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.513" />
<Button
android:id="@+id/decrypt_button"
style="@style/Widget.AppCompat.Button"
android:layout_width="152dp"
android:layout_height="59dp"
android:layout_marginStart="161dp"
android:layout_marginTop="72dp"
android:layout_marginEnd="162dp"
android:background="#00FFFFFF"
android:backgroundTint="#001C1C1C"
android:hapticFeedbackEnabled="false"
android:text="Decrypt"
android:textColor="@color/colorPrimary"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passphrase_input" />
<Button
android:id="@+id/back"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="163dp"
android:layout_marginEnd="160dp"
android:layout_marginBottom="30dp"
android:fontFamily="sans-serif-light"
android:text="Back"
android:textColor="#AAAAAA"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/AppTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context=".ViewActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:titleMargin="0dp">
</androidx.appcompat.widget.Toolbar>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal">
<ImageButton
android:id="@+id/back"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="56dp"
android:layout_height="50dp"
android:rotation="180"
android:src="@drawable/baseline_arrow_forward_white_36"
android:text="" />
<TextView
android:id="@+id/title_label"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:fontFamily="sans-serif-black"
android:paddingTop="8dp"
android:text="TITLE"
android:textAlignment="viewStart"
android:textColor="@color/colorPrimary"
android:textSize="24sp" />
</LinearLayout>
<ScrollView
android:id="@+id/scrollView2"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/fa_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/url_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Login URL"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/email_input" />
<TextView
android:id="@+id/email_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:singleLine="true"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/email_label" />
<TextView
android:id="@+id/username_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Username"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/password_input"
android:layout_width="288dp"
android:layout_height="68dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:inputType="textPassword"
android:singleLine="true"
android:textSize="28sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_label" />
<TextView
android:id="@+id/username_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_label" />
<TextView
android:id="@+id/password_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Password"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_input" />
<TextView
android:id="@+id/url_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="15dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/url_label" />
<TextView
android:id="@+id/email_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="E-Mail"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_input" />
<ImageButton
android:id="@+id/password_hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="@+id/password_input"
app:layout_constraintStart_toEndOf="@+id/password_input"
app:layout_constraintTop_toTopOf="@+id/password_input"
app:layout_constraintVertical_bias="0.437"
app:srcCompat="@drawable/baseline_visibility_black_36" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fa_layout"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/scrollView2">
<TextView
android:id="@+id/fa_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Two factor authentication"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/fa_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:editable="false"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="TAP TO SCAN"
android:inputType="none|textEmailAddress"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fa_label" />
<ProgressBar
android:id="@+id/fa_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="379dp"
android:layout_height="15dp"
android:layout_marginStart="16dp"
android:layout_marginTop="-10dp"
android:layout_marginEnd="16dp"
android:max="30"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fa_input" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -49,7 +49,7 @@
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView4"
android:id="@+id/print_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"

View File

@@ -6,19 +6,25 @@
</head>
<body>
<style>
@media print {
body {
max-height: 100vh;
page-break-after: always;
}
}
@font-face {
font-family: Roboto;
src: url(font/robotolight.ttf);
src: url(../font/robotolight.ttf);
font-weight: lighter;
}
@font-face {
font-family: Roboto;
src: url(font/robotoregular.ttf);
src: url(../font/robotoregular.ttf);
font-weight: normal;
}
@font-face {
font-family: Roboto;
src: url(font/robotobold.ttf);
src: url(../font/robotobold.ttf);
font-weight: bold;
}
@@ -26,6 +32,12 @@
text-align: center;
font-family: Roboto;
}
#content {
overflow: hidden;
height: 100vh;
width: 100vw;
border: 3px rgba(0, 0, 0, 0) solid;
}
h1 {
margin-top: 8rem;
font-size: 36pt;
@@ -39,29 +51,16 @@
}
img {
margin-top: 5rem;
height: 400px;
height: 300px;
}
/* Background stripes */
#red {
position: absolute;
bottom: -350px;
#bg img {
position: fixed;
bottom: 10px;
left: 0;
z-index: -1;
width: 340px;
height: 815px;
background-color: #FF2A2A;
transform: rotate(130deg);
}
#black {
position: absolute;
bottom: -320px;
right: 0;
z-index: -1;
width: 380px;
height: 815px;
background-color: #1C1C1C;
transform: rotate(-130deg);
width: 100vw;
height: auto;
}
</style>
<div id="content">
@@ -71,9 +70,10 @@
<p id="hint_title">Password hint</p>
<p>$HINT</p>
<img src="drawable/dummy_qr_code.png">
<div id="red"></div>
<div id="black"></div>
<img src="data:image/png;base64, $QRCODE">
<div id="bg">
<img src="print_background.png">
</div>
</div>
</body>
</html>