Skip to content

Commit f67c1f0

Browse files
committed
feat(heyauthn): create heyauthn package
re #284
1 parent e44ca7d commit f67c1f0

File tree

10 files changed

+649
-1
lines changed

10 files changed

+649
-1
lines changed

packages/heyauthn/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Vivek Bhupatiraju
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/heyauthn/README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<p align="center">
2+
<h1 align="center">
3+
HeyAuthn
4+
</h1>
5+
<p align="center">A library to allow developers to create and manage Semaphore identities using WebAuthn.</p>
6+
</p>
7+
8+
<p align="center">
9+
<a href="https://github.com/semaphore-protocol">
10+
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
11+
</a>
12+
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
13+
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
14+
</a>
15+
<a href="https://www.npmjs.com/package/@semaphore-protocol/heyauthn">
16+
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn?style=flat-square" />
17+
</a>
18+
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
19+
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" />
20+
</a>
21+
<a href="https://eslint.org/">
22+
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
23+
</a>
24+
<a href="https://prettier.io/">
25+
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
26+
</a>
27+
</p>
28+
29+
<div align="center">
30+
<h4>
31+
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
32+
👥 Contributing
33+
</a>
34+
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
35+
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
36+
🤝 Code of conduct
37+
</a>
38+
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
39+
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
40+
🔎 Issues
41+
</a>
42+
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
43+
<a href="https://semaphore.appliedzkp.org/discord">
44+
🗣️ Chat &amp; Support
45+
</a>
46+
</h4>
47+
</div>
48+
49+
| This library allows developers to create and manage Semaphore identities using [WebAuthn](https://webauthn.io/) as a cross-device biometric authentication in a way that is more convenient, smoother and secure than localStorage, Chrome extensions, or password manager based solutions. |
50+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51+
52+
## 🛠 Install
53+
54+
### npm or yarn
55+
56+
Install the `@semaphore-protocol/heyauthn` package with npm:
57+
58+
```bash
59+
npm i @semaphore-protocol/heyauthn
60+
```
61+
62+
or yarn:
63+
64+
```bash
65+
yarn add @semaphore-protocol/heyauthn
66+
```
67+
68+
## 📜 Usage
69+
70+
```typescript
71+
import { HeyAuthn } from "@semaphore-protocol/heyauthn"
72+
73+
// STEP 1: Configure WebAuthn options.
74+
75+
const options = {
76+
rpName: "my-app",
77+
rpID: window.location.hostname,
78+
userID: "my-id",
79+
userName: "my-name"
80+
}
81+
82+
// STEP 2: Register a new WebAuthn credential and get its Semaphore identity.
83+
84+
const { identity } = await HeyAuthn.fromRegister(options)
85+
86+
// Now you could also save the identity commitment in your DB (pseudocode).
87+
fetch("/api/register" /* Replace this with your endpoint */, {
88+
identity.commmitment
89+
// ...
90+
})
91+
92+
// STEP 3: Authenticate existing WebAuthn credential and signal.
93+
94+
const { identity } = await HeyAuthn.fromRegister(options)
95+
96+
// Get existing group and signal anonymously (pseudocode).
97+
import { Group } from "@semaphore-protocol/group"
98+
import { generateProof } from "@semaphore-protocol/proof"
99+
import { utils } from "ethers"
100+
101+
const group = new Group("42")
102+
103+
group.addMembers(memberList)
104+
105+
const signal = utils.formatBytes32String("Hey anon!")
106+
107+
generateProof(identity, group, group.id, "42", {
108+
zkeyFilePath: "./semaphore.zkey",
109+
wasmFilePath: "./semaphore.wasm"
110+
})
111+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"baseUrl": ".",
5+
"declarationDir": "dist/types"
6+
},
7+
"include": ["src"]
8+
}

packages/heyauthn/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@semaphore-protocol/heyauthn",
3+
"version": "3.2.3",
4+
"description": "A library to allow developers to create and manage Semaphore identities using WebAuthn",
5+
"license": "MIT",
6+
"main": "dist/index.node.js",
7+
"exports": {
8+
"import": "./dist/index.mjs",
9+
"require": "./dist/index.node.js"
10+
},
11+
"types": "dist/types/index.d.ts",
12+
"files": [
13+
"dist/",
14+
"src/",
15+
"LICENSE",
16+
"README.md"
17+
],
18+
"repository": "https://github.com/semaphore-protocol/semaphore",
19+
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/heyauthn",
20+
"bugs": {
21+
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
22+
},
23+
"scripts": {
24+
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
25+
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
26+
"prepublishOnly": "yarn build",
27+
"docs": "typedoc src/index.ts --out ../../docs/heyauthn"
28+
},
29+
"publishConfig": {
30+
"access": "public"
31+
},
32+
"devDependencies": {
33+
"rollup-plugin-cleanup": "^3.2.1",
34+
"rollup-plugin-typescript2": "^0.31.2",
35+
"typedoc": "^0.22.11"
36+
},
37+
"dependencies": {
38+
"@semaphore-protocol/identity": "3.2.3",
39+
"@simplewebauthn/browser": "7.2.0",
40+
"@simplewebauthn/server": "7.2.0"
41+
}
42+
}

packages/heyauthn/rollup.config.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import typescript from "rollup-plugin-typescript2"
2+
import * as fs from "fs"
3+
import cleanup from "rollup-plugin-cleanup"
4+
5+
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
6+
const banner = `/**
7+
* @module ${pkg.name}
8+
* @version ${pkg.version}
9+
* @file ${pkg.description}
10+
* @copyright Vivek Bhupatiraju 2023
11+
* @license ${pkg.license}
12+
* @see [Github]{@link ${pkg.homepage}}
13+
*/`
14+
15+
export default {
16+
input: "src/index.ts",
17+
output: [
18+
{ file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
19+
{ file: pkg.exports.import, format: "es", banner }
20+
],
21+
external: Object.keys(pkg.dependencies),
22+
plugins: [
23+
typescript({
24+
tsconfig: "./build.tsconfig.json",
25+
useTsconfigDeclarationDir: true
26+
}),
27+
cleanup({ comments: "jsdoc" })
28+
]
29+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Identity } from "@semaphore-protocol/identity"
2+
import {
3+
GenerateRegistrationOptionsOpts as RegistrationOptions,
4+
GenerateAuthenticationOptionsOpts as AuthenticationOptions
5+
} from "@simplewebauthn/server"
6+
7+
import HeyAuthn from "./heyAuthn"
8+
9+
jest.mock("@simplewebauthn/browser", () => ({
10+
startRegistration: async () => ({
11+
id: "my-new-credential",
12+
rawId: "my-new-credential",
13+
response: {
14+
clientDataJSON: "",
15+
attestationObject: ""
16+
},
17+
clientExtensionResults: {},
18+
type: "public-key"
19+
}),
20+
startAuthentication: async () => ({
21+
id: "my-existing-credential",
22+
rawId: "my-existing-credential",
23+
response: {
24+
clientDataJSON: "",
25+
attestationObject: ""
26+
},
27+
clientExtensionResults: {},
28+
type: "public-key"
29+
})
30+
}))
31+
32+
describe("HeyAuthn", () => {
33+
describe("# getIdentity", () => {
34+
it("Should get the identity of the HeyAuthn instance", async () => {
35+
const expectedIdentity = new Identity()
36+
const heyAuthn = new HeyAuthn(expectedIdentity)
37+
const identity = heyAuthn.getIdentity()
38+
39+
expect(expectedIdentity.toString()).toEqual(identity.toString())
40+
})
41+
})
42+
43+
describe("# fromRegister", () => {
44+
const options: RegistrationOptions = {
45+
rpName: "my-app",
46+
rpID: "hostname",
47+
userID: "my-id",
48+
userName: "my-name"
49+
}
50+
51+
it("Should create an identity identical to the one created registering credential", async () => {
52+
const { identity } = await HeyAuthn.fromRegister(options)
53+
const expectedIdentity = new Identity("my-new-credential")
54+
55+
expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor)
56+
expect(expectedIdentity.nullifier).toEqual(identity.nullifier)
57+
expect(expectedIdentity.commitment).toEqual(identity.commitment)
58+
})
59+
})
60+
61+
describe("# fromAuthenticate", () => {
62+
const options: AuthenticationOptions = {
63+
rpID: "hostname"
64+
}
65+
66+
it("Should create an identity identical to the one created authenticating credential", async () => {
67+
const { identity } = await HeyAuthn.fromAuthenticate(options)
68+
const expectedIdentity = new Identity("my-existing-credential")
69+
70+
expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor)
71+
expect(expectedIdentity.nullifier).toEqual(identity.nullifier)
72+
expect(expectedIdentity.commitment).toEqual(identity.commitment)
73+
})
74+
})
75+
})

packages/heyauthn/src/heyAuthn.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
generateAuthenticationOptions,
3+
generateRegistrationOptions,
4+
GenerateRegistrationOptionsOpts as RegistrationOptions,
5+
GenerateAuthenticationOptionsOpts as AuthenticationOptions
6+
} from "@simplewebauthn/server"
7+
import { startAuthentication, startRegistration } from "@simplewebauthn/browser"
8+
import { Identity } from "@semaphore-protocol/identity"
9+
10+
export default class HeyAuthn {
11+
private _identity: Identity
12+
13+
constructor(identity: Identity) {
14+
this._identity = identity
15+
}
16+
17+
/**
18+
* Registers a new WebAuthn credential and returns its HeyAuthn instance.
19+
*
20+
* @param {GenerateRegistrationOptionsOpts} options - WebAuthn options for registering a new credential.
21+
* @returns A HeyAuthn instance with the newly registered credential.
22+
*/
23+
public static async fromRegister(options: RegistrationOptions) {
24+
const registrationOptions = generateRegistrationOptions(options)
25+
const { id } = await startRegistration(registrationOptions)
26+
27+
const identity = new Identity(id)
28+
29+
return new HeyAuthn(identity)
30+
}
31+
32+
/**
33+
* Authenticates an existing WebAuthn credential and returns its HeyAuthn instance.
34+
*
35+
* @param {GenerateAuthenticationOptionsOpts} options - WebAuthn options for authenticating an existing credential.
36+
* @returns A HeyAuthn instance with the existing credential.
37+
*/
38+
public static async fromAuthenticate(options: AuthenticationOptions) {
39+
const authenticationOptions = generateAuthenticationOptions(options)
40+
const { id } = await startAuthentication(authenticationOptions)
41+
42+
const identity = new Identity(id)
43+
44+
return new HeyAuthn(identity)
45+
}
46+
47+
/**
48+
* Returns the Semaphore identity instance.
49+
* @returns The Semaphore identity.
50+
*/
51+
public get identity(): Identity {
52+
return this._identity
53+
}
54+
55+
/**
56+
* Returns the Semaphore identity instance.
57+
* @returns The Semaphore identity.
58+
*/
59+
public getIdentity(): Identity {
60+
return this._identity
61+
}
62+
}

packages/heyauthn/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Identity } from "@semaphore-protocol/identity"
2+
import { GenerateAuthenticationOptionsOpts, GenerateRegistrationOptionsOpts } from "@simplewebauthn/server"
3+
import HeyAuthn from "./heyAuthn"
4+
5+
export {
6+
HeyAuthn,
7+
GenerateRegistrationOptionsOpts as RegistrationOptions,
8+
GenerateAuthenticationOptionsOpts as AuthenticationOptions,
9+
Identity
10+
}

packages/heyauthn/tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"include": ["src", "rollup.config.ts"]
4+
}

0 commit comments

Comments
 (0)