-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from akopachov/ghi-13
Implemented #13
- Loading branch information
Showing
6 changed files
with
265 additions
and
101 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,93 +1,92 @@ | ||
{ | ||
"name": "flipper-authenticator-companion", | ||
"version": "1.1.1", | ||
"private": true, | ||
"description": "Companion application for Flipper Authenticator software-based TOTP authenticator for Flipper Zero device", | ||
"main": "src/electron.cjs", | ||
"type": "module", | ||
"author": "Alexander Kopachov (@akopachov)", | ||
"scripts": { | ||
"dev": "cross-env NODE_ENV=dev pnpm run dev:all", | ||
"dev:all": "concurrently -n=svelte,electron -c='#ff3e00',blue \"pnpm run dev:svelte\" \"pnpm run dev:electron\"", | ||
"dev:svelte": "vite dev", | ||
"dev:electron": "electron src/electron.cjs", | ||
"build": "cross-env NODE_ENV=production pnpm run build:svelte && pnpm build:electron", | ||
"build:svelte": "vite build", | ||
"build:electron": "electron-builder --config build.config.json", | ||
"lint": "eslint src/", | ||
"lint:fix": "eslint src/ --fix", | ||
"update:icons": "node update-aegis-icons.cjs" | ||
}, | ||
"browserslist": [ | ||
"Chrome 89" | ||
], | ||
"engines": { | ||
"node": ">=18", | ||
"pnpm": ">=8", | ||
"npm": "please-use-pnpm", | ||
"yarn": "please-use-pnpm" | ||
}, | ||
"devDependencies": { | ||
"@floating-ui/dom": "^1.5.1", | ||
"@skeletonlabs/skeleton": "^2.0.0", | ||
"@skeletonlabs/tw-plugin": "^0.1.0", | ||
"@sveltejs/adapter-static": "2.0.3", | ||
"@sveltejs/kit": "1.24.0", | ||
"@tailwindcss/forms": "^0.5.6", | ||
"@tailwindcss/typography": "^0.5.9", | ||
"@types/node": "^20.5.7", | ||
"@types/papaparse": "^5.3.8", | ||
"@types/uuid": "^9.0.3", | ||
"@typescript-eslint/eslint-plugin": "^6.5.0", | ||
"@typescript-eslint/parser": "^6.5.0", | ||
"async-sema": "^3.1.1", | ||
"autoprefixer": "^10.4.15", | ||
"concurrently": "^8.2.1", | ||
"cross-env": "^7.0.3", | ||
"delay": "^6.0.0", | ||
"electron": "^26.1.0", | ||
"electron-builder": "^24.6.3", | ||
"electron-connect": "^0.6.3", | ||
"electron-packager": "^17.1.2", | ||
"electron-reloader": "^1.2.3", | ||
"escape-string-regexp": "^5.0.0", | ||
"eslint": "^8.48.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-prettier": "^5.0.0", | ||
"eslint-plugin-svelte": "^2.33.0", | ||
"papaparse": "^5.4.1", | ||
"postcss": "^8.4.29", | ||
"postcss-load-config": "^4.0.1", | ||
"prettier": "^3.0.3", | ||
"prettier-plugin-svelte": "^3.0.3", | ||
"qr-scanner": "^1.4.2", | ||
"sass": "^1.66.1", | ||
"smart-buffer": "^4.2.0", | ||
"svelte": "^4.2.0", | ||
"svelte-check": "^3.5.1", | ||
"svelte-dnd-action": "^0.9.26", | ||
"svelte-eslint-parser": "^0.33.0", | ||
"svelte-preprocess": "^5.0.4", | ||
"tailwindcss": "^3.3.3", | ||
"tslib": "^2.6.2", | ||
"typescript": "^5.2.2", | ||
"url-otpauth-ng": "^3.1.0", | ||
"utc-offsets": "^1.0.0", | ||
"uuid": "^9.0.0", | ||
"vite": "^4.4.9", | ||
"vite-plugin-electron-renderer": "^0.14.5", | ||
"vite-plugin-tailwind-purgecss": "^0.1.3" | ||
}, | ||
"dependencies": { | ||
"electron-log": "^4.4.8", | ||
"electron-serve": "^1.1.0", | ||
"electron-store": "^8.1.0", | ||
"electron-updater": "^6.1.1", | ||
"electron-window-state": "^5.0.3", | ||
"node-screenshots": "^0.1.6", | ||
"serialport": "^12.0.0" | ||
}, | ||
"optionalDependencies": { | ||
"adm-zip": "^0.5.10" | ||
} | ||
} | ||
{ | ||
"name": "flipper-authenticator-companion", | ||
"version": "1.1.1", | ||
"private": true, | ||
"description": "Companion application for Flipper Authenticator software-based TOTP authenticator for Flipper Zero device", | ||
"main": "src/electron.cjs", | ||
"type": "module", | ||
"author": "Alexander Kopachov (@akopachov)", | ||
"scripts": { | ||
"dev": "cross-env NODE_ENV=dev pnpm run dev:all", | ||
"dev:all": "concurrently -n=svelte,electron -c='#ff3e00',blue \"pnpm run dev:svelte\" \"pnpm run dev:electron\"", | ||
"dev:svelte": "vite dev", | ||
"dev:electron": "electron src/electron.cjs", | ||
"build": "cross-env NODE_ENV=production pnpm run build:svelte && pnpm build:electron", | ||
"build:svelte": "vite build", | ||
"build:electron": "electron-builder --config build.config.json", | ||
"lint": "eslint src/", | ||
"lint:fix": "eslint src/ --fix", | ||
"update:icons": "node update-aegis-icons.cjs" | ||
}, | ||
"browserslist": [ | ||
"Chrome 89" | ||
], | ||
"engines": { | ||
"node": ">=18", | ||
"pnpm": ">=8", | ||
"npm": "please-use-pnpm", | ||
"yarn": "please-use-pnpm" | ||
}, | ||
"devDependencies": { | ||
"@floating-ui/dom": "^1.5.1", | ||
"@skeletonlabs/skeleton": "^2.0.0", | ||
"@skeletonlabs/tw-plugin": "^0.1.0", | ||
"@sveltejs/adapter-static": "2.0.3", | ||
"@sveltejs/kit": "1.24.0", | ||
"@tailwindcss/forms": "^0.5.6", | ||
"@tailwindcss/typography": "^0.5.9", | ||
"@types/node": "^20.5.7", | ||
"@types/papaparse": "^5.3.8", | ||
"@types/uuid": "^9.0.3", | ||
"@typescript-eslint/eslint-plugin": "^6.5.0", | ||
"@typescript-eslint/parser": "^6.5.0", | ||
"async-sema": "^3.1.1", | ||
"autoprefixer": "^10.4.15", | ||
"concurrently": "^8.2.1", | ||
"cross-env": "^7.0.3", | ||
"delay": "^6.0.0", | ||
"electron": "^26.1.0", | ||
"electron-builder": "^24.6.3", | ||
"electron-connect": "^0.6.3", | ||
"electron-packager": "^17.1.2", | ||
"electron-reloader": "^1.2.3", | ||
"escape-string-regexp": "^5.0.0", | ||
"eslint": "^8.48.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-prettier": "^5.0.0", | ||
"eslint-plugin-svelte": "^2.33.0", | ||
"papaparse": "^5.4.1", | ||
"postcss": "^8.4.29", | ||
"postcss-load-config": "^4.0.1", | ||
"prettier": "^3.0.3", | ||
"prettier-plugin-svelte": "^3.0.3", | ||
"qr-scanner": "^1.4.2", | ||
"sass": "^1.66.1", | ||
"smart-buffer": "^4.2.0", | ||
"svelte": "^4.2.0", | ||
"svelte-check": "^3.5.1", | ||
"svelte-dnd-action": "^0.9.26", | ||
"svelte-eslint-parser": "^0.33.0", | ||
"svelte-preprocess": "^5.0.4", | ||
"tailwindcss": "^3.3.3", | ||
"tslib": "^2.6.2", | ||
"typescript": "^5.2.2", | ||
"utc-offsets": "^1.0.0", | ||
"uuid": "^9.0.0", | ||
"vite": "^4.4.9", | ||
"vite-plugin-electron-renderer": "^0.14.5", | ||
"vite-plugin-tailwind-purgecss": "^0.1.3" | ||
}, | ||
"dependencies": { | ||
"electron-log": "^4.4.8", | ||
"electron-serve": "^1.1.0", | ||
"electron-store": "^8.1.0", | ||
"electron-updater": "^6.1.1", | ||
"electron-window-state": "^5.0.3", | ||
"node-screenshots": "^0.1.6", | ||
"serialport": "^12.0.0" | ||
}, | ||
"optionalDependencies": { | ||
"adm-zip": "^0.5.10" | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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 |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export enum ErrorType { | ||
InvalidIssuer = 0, | ||
InvalidLabel = 1, | ||
InvalidProtocol = 2, | ||
MissingAccountName = 3, | ||
MissingCounter = 4, | ||
MissingIssuer = 5, | ||
MissingSecretKey = 6, | ||
UnknownOtp = 7, | ||
InvalidDigits = 8, | ||
UnknownAlgorithm = 9, | ||
} |
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 |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ErrorType } from './error-type'; | ||
|
||
export class OtpAuthInvalidUrl extends Error { | ||
constructor(errorType: ErrorType) { | ||
super(); | ||
this.name = 'OtpauthInvalidURL'; | ||
this.message = `Given otpauth:// URL is invalid. (Error ${ErrorType[errorType]})`; | ||
} | ||
} |
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 |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// Forked from https://github.com/huihuimoe/url-otpauth-ng | ||
// Updated to add Steam support | ||
|
||
import { ErrorType } from './error-type'; | ||
import { OtpAuthInvalidUrl } from './otp-auth-invalid-url'; | ||
|
||
const PossibleType = <const>['totp', 'hotp', 'yaotp']; | ||
const PossibleDigits = <const>[5, 6, 8]; | ||
const PossibleAlgorithms = <const>['SHA1', 'SHA256', 'SHA512', 'Steam']; | ||
|
||
const isInArray = <T, A extends T>(item: T, array: ReadonlyArray<A>): item is A => array.includes(item as A); | ||
|
||
export type OtpUrlParseResult = { | ||
type: (typeof PossibleType)[number]; | ||
account: string; | ||
key: string; | ||
issuer: string; | ||
digits: (typeof PossibleDigits)[number]; | ||
algorithm: (typeof PossibleAlgorithms)[number]; | ||
period: number; | ||
counter?: number; | ||
}; | ||
|
||
const DefaultOtpValue: Pick<OtpUrlParseResult, 'digits' | 'algorithm' | 'period'> = { | ||
digits: 6, | ||
algorithm: 'SHA1', | ||
period: 30, | ||
}; | ||
|
||
/** | ||
* Parses an OTPAuth URI. | ||
* | ||
* Parses an URL as described in Google Authenticator's "KeyUriFormat" document (see: | ||
* [https://github.com/google/google-authenticator/wiki/Key-Uri-Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)) | ||
* and returns an object that contains the following properties: | ||
* | ||
**/ | ||
export function parse(rawUrl: string | URL): OtpUrlParseResult { | ||
const decode = decodeURIComponent; | ||
|
||
const parsedOtpValues: Partial<OtpUrlParseResult> = {}; | ||
|
||
let parsed = new URL(rawUrl); | ||
|
||
if (parsed.protocol !== 'otpauth:') { | ||
throw new OtpAuthInvalidUrl(ErrorType.InvalidProtocol); | ||
} | ||
|
||
// hack for Chrome | ||
parsed.protocol = 'ftp'; | ||
parsed = new URL(parsed); | ||
|
||
const otpAlgo = decode(parsed.host); | ||
|
||
if (!isInArray(otpAlgo, PossibleType)) { | ||
throw new OtpAuthInvalidUrl(ErrorType.UnknownOtp); | ||
} | ||
|
||
parsedOtpValues.type = otpAlgo; | ||
|
||
// | ||
// Label (contains account name, may contain issuer) | ||
// | ||
|
||
const label = parsed.pathname.substring(1); | ||
// if you want to support mutli commas in label | ||
// const labelComponents = label.split(~label.indexOf(':') ? /:(.*)/ : /%3A(.*)/, 2) | ||
const labelComponents = label.split(~label.indexOf(':') ? ':' : '%3A'); | ||
let issuer = ''; | ||
let account = ''; | ||
|
||
if (labelComponents.length === 1) { | ||
account = decode(labelComponents[0]); | ||
} else if (labelComponents.length === 2) { | ||
issuer = decode(labelComponents[0]); | ||
account = decode(labelComponents[1]); | ||
} else { | ||
throw new OtpAuthInvalidUrl(ErrorType.InvalidLabel); | ||
} | ||
|
||
if (account.length < 1) { | ||
throw new OtpAuthInvalidUrl(ErrorType.MissingAccountName); | ||
} | ||
|
||
if (labelComponents.length === 2 && issuer.length < 1) { | ||
throw new OtpAuthInvalidUrl(ErrorType.InvalidIssuer); | ||
} | ||
|
||
parsedOtpValues.account = account; | ||
|
||
const parameters = parsed.searchParams; | ||
|
||
// Secret key | ||
if (!parameters.has('secret')) { | ||
throw new OtpAuthInvalidUrl(ErrorType.MissingSecretKey); | ||
} | ||
|
||
parsedOtpValues.key = parameters.get('secret')!; | ||
|
||
// Issuer | ||
if (parameters.has('issuer') && issuer && parameters.get('issuer') !== issuer && issuer !== 'steamctl') { | ||
// If present, it must be equal to the "issuer" specified in the label. | ||
// Exception - steamctl | ||
throw new OtpAuthInvalidUrl(ErrorType.InvalidIssuer); | ||
} | ||
|
||
parsedOtpValues.issuer = parameters.get('issuer') || issuer; | ||
|
||
// OTP digits | ||
if (parameters.has('digits')) { | ||
const parsedDigits = parseInt(parameters.get('digits')!) || 0; | ||
if (isInArray(parsedDigits, PossibleDigits)) { | ||
parsedOtpValues.digits = parsedDigits; | ||
} else { | ||
throw new OtpAuthInvalidUrl(ErrorType.InvalidDigits); | ||
} | ||
} | ||
|
||
// Algorithm to create hash | ||
if (parameters.has('algorithm')) { | ||
const algo = parameters.get('algorithm')!; | ||
if (isInArray(algo, PossibleAlgorithms)) { | ||
// Optional 'algorithm' parameter. | ||
parsedOtpValues.algorithm = algo; | ||
} else { | ||
throw new OtpAuthInvalidUrl(ErrorType.UnknownAlgorithm); | ||
} | ||
} else if (issuer === 'steamctl') { | ||
parsedOtpValues.algorithm = 'Steam'; | ||
} | ||
|
||
// Period (only for TOTP) | ||
if (otpAlgo === 'totp') { | ||
// Optional 'period' parameter for TOTP. | ||
if (parameters.has('period')) { | ||
parsedOtpValues.period = parseInt(parameters.get('period')!) || 0; | ||
} | ||
} | ||
|
||
// Counter (only for HOTP) | ||
if (otpAlgo === 'hotp') { | ||
if (parameters.has('counter')) { | ||
parsedOtpValues.counter = parseInt(parameters.get('counter')!) || 0; | ||
} else { | ||
// We require the 'counter' parameter for HOTP. | ||
throw new OtpAuthInvalidUrl(ErrorType.MissingCounter); | ||
} | ||
} | ||
|
||
return { ...DefaultOtpValue, ...parsedOtpValues } as OtpUrlParseResult; | ||
} |
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