-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update dependencies Update README.md Add config options - Show password - Force all character types - Use legacy alphabet - Password length
- Loading branch information
Showing
8 changed files
with
6,622 additions
and
6,458 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
react-with-typescript | ||
# SSS | ||
|
||
Password manager based on name hashes instead on storage | ||
|
||
[Try the online version](https://sss.pagoru.es/) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,32 @@ | ||
{ | ||
"name": "sss.pagoru.es", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "", | ||
"main": "src/index.tsx", | ||
"scripts": { | ||
"build": "./node_modules/.bin/webpack -p", | ||
"start": "./node_modules/.bin/webpack-dev-server --inline --hot --history-api-fallback --open --content-base ./public" | ||
"start_": "./node_modules/.bin/webpack-dev-server --hot --history-api-fallback --open --content-base ./public", | ||
"start": "npx webpack serve --mode development" | ||
}, | ||
"author": "pablo", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@types/react": "^16.3.11", | ||
"@types/react-dom": "^16.0.5", | ||
"awesome-typescript-loader": "^3.5.0", | ||
"css-loader": "^0.28.11", | ||
"less": "^2.7.3", | ||
"@types/react": "^17.0.30", | ||
"@types/react-dom": "^17.0.9", | ||
"css-loader": "^6.4.0", | ||
"less-loader": "^4.1.0", | ||
"qrcode": "^1.2.0", | ||
"react-copy-to-clipboard": "^5.0.1", | ||
"style-loader": "^0.19.1", | ||
"super-secret-settings": "^3.0.2", | ||
"typescript": "^2.8.1", | ||
"webpack": "^3.11.0", | ||
"webpack-dev-server": "^2.11.2" | ||
"style-loader": "^3.3.0", | ||
"ts-loader": "^9.2.6", | ||
"webpack": "^5.58.2", | ||
"webpack-cli": "^4.9.0", | ||
"webpack-dev-server": "^4.3.1" | ||
}, | ||
"dependencies": { | ||
"react": "^16.3.2", | ||
"react-dom": "^16.3.2" | ||
"buffer": "^6.0.3", | ||
"random-seed": "^0.3.0", | ||
"react": "^17.0.2", | ||
"react-copy-to-clipboard": "^5.0.4", | ||
"react-dom": "^17.0.2", | ||
"super-secret-settings": "file:../super-secret-settings" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,214 @@ | ||
import * as React from 'react' | ||
import * as ReactDOM from 'react-dom' | ||
import { CopyToClipboard } from 'react-copy-to-clipboard'; | ||
import './index.less' | ||
import * as React from "react"; | ||
import {useEffect, useState} from "react"; | ||
import {CopyToClipboard} from 'react-copy-to-clipboard'; | ||
import {getHash, getPassword, getCssColor} from 'super-secret-settings'; | ||
|
||
import { getHash, getPassword } from 'super-secret-settings'; | ||
const main_img = 'https://robohash.org/Bender'; | ||
|
||
import './index.less' | ||
export function Application() { | ||
const [secret, setSecret] = useState(''); | ||
const [service, setService] = useState(''); | ||
const [openConfig, setOpenConfig] = useState(false); | ||
const [configLoaded, setConfigLoaded] = useState(false); | ||
const [lenght, setLenght] = useState(12); | ||
const [forceAllChars, setForceAllChars] = useState(false); | ||
const [legacyAlphabet, setLegacyAlphabet] = useState(true); | ||
const [showPassword, setShowPassword] = useState(false); | ||
|
||
const qr_url = 'https://api.qrserver.com/v1/create-qr-code/?size=500x500&data='; | ||
const qr_url2 = 'https://loremflickr.com/500/500?lock='; | ||
const qr_static = 'https://instagram.fmad7-1.fna.fbcdn.net/vp/40e87239ca4b09b3d13fe05a553df631/5B65842E/t51.2885-15/e35/30602671_269685373571513_4681094377852895232_n.jpg'; | ||
useEffect(() => { | ||
try { | ||
// If cookies are disable this will crash, there is no way to detect it :( | ||
if (configLoaded) { | ||
window.localStorage.setItem('sss.password_length', `${lenght}`); | ||
window.localStorage.setItem('sss.force_all_chars', `${forceAllChars}`); | ||
window.localStorage.setItem('sss.legacy_alphabet', `${legacyAlphabet}`); | ||
window.localStorage.setItem('sss.show_password', `${showPassword}`); | ||
} else { | ||
let password_length = window.localStorage.getItem('sss.password_length') || '14'; | ||
let force_all_chars = window.localStorage.getItem('sss.force_all_chars') || 'false'; | ||
let legacy_alphabet = window.localStorage.getItem('sss.legacy_alphabet') || 'true'; | ||
let show_password = window.localStorage.getItem('sss.show_password') || 'true'; | ||
|
||
export default class Application extends React.Component<{}, {}> { | ||
setLenght(+password_length); | ||
setForceAllChars(force_all_chars == 'true'); | ||
setLegacyAlphabet(legacy_alphabet == 'true'); | ||
setShowPassword(show_password == 'true'); | ||
setConfigLoaded(true); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}); | ||
|
||
constructor(props, context?: any){ | ||
super(props, context); | ||
const alphabet = legacyAlphabet | ||
? 'ABCDFGHIJKLMNOPQRSTUVWXYZabdfghijklmnopqrstuvwxyz1234567890' | ||
: 'ABCDEFGHIJKLMNÑOPQRSTUVWXYZabcdefghijklmnñopqrstuvwxyz1234567890!@#$%*()_+=-€¡?¿[]{}",./ç<>| '; | ||
|
||
this.state = { | ||
secret: '', | ||
service: '', | ||
hash: '00000000', | ||
password: '' | ||
}; | ||
let password = getPassword(secret, service, lenght, alphabet); | ||
|
||
this.onChangeSecret = this.onChangeSecret.bind(this); | ||
this.onChangeService = this.onChangeService.bind(this); | ||
} | ||
if (!hasAllChars(password, !legacyAlphabet)) { | ||
console.log(password); | ||
} | ||
|
||
onChangeSecret(event: any) { | ||
const secret = event.target.value; | ||
this.setState({ | ||
secret: secret, | ||
hash: getHash(secret).substr(0, 8), | ||
password: getPassword(secret, this.state['service']) | ||
}); | ||
if (forceAllChars && password.length >= 4) { | ||
while (!hasAllChars(password, !legacyAlphabet)) { | ||
password = getPassword(secret, password, lenght, alphabet); | ||
} | ||
} | ||
|
||
onChangeService(event: any) { | ||
const service = event.target.value; | ||
this.setState({ | ||
service: service, | ||
hash: getHash(this.state['secret']).substr(0, 8), | ||
password: getPassword(this.state['secret'], service) | ||
}); | ||
} | ||
const secretHash = getHash(secret).substr(0, 8); | ||
const serviceHash = getHash(service).substr(0, 8); | ||
const passwordHash = getHash(password).substr(0, 8); | ||
|
||
render() { | ||
document.body.style.backgroundColor = `#${this.state['hash'].substr(0, 6)}`; | ||
return ( | ||
<div className="robot"> | ||
<div className="inputs"> | ||
<input id="secret" placeholder="secret" type="password" onChange={this.onChangeSecret} autoFocus/> | ||
<input id="service" placeholder="service" value={this.state['service']} onChange={this.onChangeService}/> | ||
<div> | ||
</div> | ||
<label id="hexadecimalSeed" className="noselect">{this.state['hash']}</label> | ||
</div> | ||
<CopyToClipboard text={this.state['password']}> | ||
<img id="robotRock" src={qr_static}/> | ||
</CopyToClipboard> | ||
</div> | ||
); | ||
} | ||
const secretColor = secret ? getCssColor(secret, 50, 80) : 'white'; | ||
const serviceColor = service ? getCssColor(service, 50, 80) : 'white'; | ||
const passwordColor = secret || service ? getCssColor(password) : '#249D9F'; | ||
|
||
const changeSecret = (event) => setSecret(event.target.value); | ||
const changeService = (event) => setService(event.target.value); | ||
const setPassLength = (text) => { | ||
let int = +text; | ||
if (isNaN(int) || int < 0 || int > 999) return; | ||
setLenght(int); | ||
} | ||
|
||
const toggleConfig = () => setOpenConfig(!openConfig); | ||
|
||
document.body.style.backgroundColor = passwordColor; | ||
|
||
return ( | ||
<div className="robot"> | ||
<CopyToClipboard text={password}> | ||
<img id="robot-img" src={main_img} alt=""/> | ||
</CopyToClipboard> | ||
|
||
<div className="inputs"> | ||
<div className='input-wrapper'> | ||
<input id="secret" | ||
placeholder="secret" | ||
type="password" | ||
value={secret} | ||
style={{backgroundColor: secretColor}} | ||
onChange={changeSecret} | ||
autoFocus/> | ||
|
||
<label className="hexadecimal-seed noselect" title='Check hash'> | ||
{secretHash} | ||
</label> | ||
</div> | ||
|
||
<div className='input-wrapper'> | ||
<input id="service" | ||
placeholder="service" | ||
value={service} | ||
style={{backgroundColor: serviceColor}} | ||
onChange={changeService}/> | ||
<div/> | ||
<label className="hexadecimal-seed noselect" title='Check hash'> | ||
{serviceHash} | ||
</label> | ||
</div> | ||
|
||
<div className='input-wrapper'> | ||
<input id="password-output" | ||
disabled={true} | ||
placeholder="password" | ||
type={showPassword ? 'text' : 'password'} | ||
style={{backgroundColor: 'white'}} | ||
value={secret || service ? password : ''} | ||
onChange={() => null}/> | ||
<div/> | ||
<label className="hexadecimal-seed noselect" title='Check hash'> | ||
{secret || service ? passwordHash : ''} | ||
</label> | ||
</div> | ||
</div> | ||
|
||
<div className='last-row'> | ||
<CopyToClipboard text={secret.length ? password : ''}> | ||
<button className={secret.length ? 'copy-btn' : 'copy-btn disable'}> | ||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" | ||
className="bi bi-clipboard" viewBox="0 0 16 16"> | ||
<path | ||
d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> | ||
<path | ||
d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> | ||
</svg> | ||
<div>Copy to clipboard</div> | ||
</button> | ||
</CopyToClipboard> | ||
|
||
<button className='config' onClick={toggleConfig}> | ||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" | ||
className="bi bi-gear-fill" viewBox="0 0 16 16"> | ||
<path | ||
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/> | ||
</svg> | ||
</button> | ||
</div> | ||
|
||
{ | ||
openConfig && | ||
<div className='config-panel'> | ||
|
||
<div className='field'> | ||
<label> | ||
<input type="checkbox" | ||
checked={showPassword} | ||
onChange={() => setShowPassword(!showPassword)}/> | ||
Show password | ||
</label> | ||
</div> | ||
|
||
<div className='field'> | ||
<label> | ||
<input type="checkbox" | ||
checked={forceAllChars} | ||
onChange={() => setForceAllChars(!forceAllChars)}/> | ||
Force all character types | ||
</label> | ||
</div> | ||
|
||
<div className='field'> | ||
<label> | ||
<input type="checkbox" | ||
checked={legacyAlphabet} | ||
onChange={() => setLegacyAlphabet(!legacyAlphabet)}/> | ||
Use legacy alphabet | ||
</label> | ||
</div> | ||
|
||
<div className='field'> | ||
<label> | ||
<input type="number" | ||
min={1} | ||
max={512} | ||
step={1} | ||
value={lenght} | ||
onChange={e => setPassLength(e.target.value)}/> | ||
Password length | ||
</label> | ||
|
||
</div> | ||
</div> | ||
} | ||
</div> | ||
); | ||
} | ||
|
||
function hasAllChars(text, includeSpecial) { | ||
let lower = false; | ||
let upper = false; | ||
let number = false; | ||
let special = false; | ||
|
||
text.split('').forEach(char => { | ||
lower = lower || /[a-z]/.test(char); | ||
upper = upper || /[A-Z]/.test(char); | ||
number = number || /\d/.test(char); | ||
special = special || /[^A-Za-z0-9]/.test(char); | ||
}); | ||
|
||
return lower && upper && number && (!includeSpecial || special); | ||
} |
Oops, something went wrong.