1
0
mirror of https://github.com/oceanprotocol-archive/squid-js.git synced 2024-02-02 15:31:51 +01:00

Add Auth module.

This commit is contained in:
Pedro Gutiérrez 2019-05-08 02:53:59 +02:00
parent 325e339b56
commit b236cda7dd
7 changed files with 250 additions and 6 deletions

16
package-lock.json generated
View File

@ -1487,6 +1487,12 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
"core-js": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-0.8.4.tgz",
"integrity": "sha1-wiZl8eDRucPF4bCNq9HxCGleT88=",
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -4735,6 +4741,16 @@
"resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.8.0.tgz",
"integrity": "sha512-Gwj4KnJOW15YeTJKO5frFd/WDO5Mc0zxXqL9oHx3+e9rBqW8EVARqQHSaIXznUdljrD6pvbNGW2ZGXKPEfYJfw=="
},
"mock-local-storage": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/mock-local-storage/-/mock-local-storage-1.1.8.tgz",
"integrity": "sha512-x/LPtSBqSROQGjJn60Fp0+rgOHZuuQZysgeSYfhG2l/moD0LLW60bKi4KpmCnNs9S3l0cbS5cmzQFzeKDL7RaQ==",
"dev": true,
"requires": {
"core-js": "^0.8.3",
"global": "^4.3.2"
}
},
"mout": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/mout/-/mout-0.11.1.tgz",

View File

@ -6,9 +6,9 @@
"typings": "./dist/node/squid.d.ts",
"browser": "./dist/browser/squid.cjs2.min.js",
"scripts": {
"test": "mocha",
"test:watch": "mocha -w --watch-extensions js,ts,json",
"test:cover": "nyc --report-dir coverage/unit mocha",
"test": "mocha --require mock-local-storage",
"test:watch": "mocha --require mock-local-storage -w --watch-extensions js,ts,json",
"test:cover": "nyc --report-dir coverage/unit mocha --require mock-local-storage",
"integration": "mocha --opts integration/mocha.opts",
"integration:nile": "export NETWORK_NAME=nile; mocha --opts integration/mocha.opts",
"integration:duero": "export NETWORK_NAME=duero; mocha --opts integration/mocha.opts",
@ -85,6 +85,7 @@
"cross-env": "^5.2.0",
"lcov-result-merger": "^3.1.0",
"mocha": "^6.1.2",
"mock-local-storage": "^1.1.8",
"nyc": "^14.1.0",
"source-map-support": "^0.5.12",
"truffle-hdwallet-provider": "^1.0.6",

View File

@ -1,6 +1,7 @@
import { OceanAccounts } from "./OceanAccounts"
import { OceanAgreements } from "./OceanAgreements"
import { OceanAssets } from "./OceanAssets"
import { OceanAuth } from "./OceanAuth"
import { OceanSecretStore } from "./OceanSecretStore"
import { OceanTokens } from "./OceanTokens"
import { OceanUtils } from "./utils/OceanUtils"
@ -39,6 +40,7 @@ export class Ocean extends Instantiable {
instance.aquarius = new Aquarius(instanceConfig)
instance.accounts = await OceanAccounts.getInstance(instanceConfig)
instance.auth = await OceanAuth.getInstance(instanceConfig)
instance.assets = await OceanAssets.getInstance(instanceConfig)
instance.agreements = await OceanAgreements.getInstance(instanceConfig)
instance.secretStore = await OceanSecretStore.getInstance(instanceConfig)
@ -73,6 +75,12 @@ export class Ocean extends Instantiable {
*/
public accounts: OceanAccounts
/**
* Ocean auth submodule
* @type {OceanAuth}
*/
public auth: OceanAuth
/**
* Ocean assets submodule
* @type {OceanAssets}

118
src/ocean/OceanAuth.ts Normal file
View File

@ -0,0 +1,118 @@
import Account from "./Account"
import { Instantiable, InstantiableConfig } from "../Instantiable.abstract"
// TODO: be able to read it from config
const defaultAuthMessage = "Ocean Protocol Authentication"
const localStorageKey = "SquidTokens"
/**
* Tokens submodule of Ocean Protocol.
*/
export class OceanAuth extends Instantiable {
/**
* Returns the instance of OceanAuth.
* @return {Promise<OceanAuth>}
*/
public static async getInstance(config: InstantiableConfig): Promise<OceanAuth> {
const instance = new OceanAuth()
instance.setInstanceConfig(config)
return instance
}
/**
* Returns a token for a account.
* @param {Account} account Signer account.
* @return {Promise<string>} Token
*/
public async get(account: Account): Promise<string> {
const time = Date.now()
const message = `${defaultAuthMessage}\n${time}`
try {
const signature = await this.ocean.utils.signature
.signText(
message,
account.getId(),
account.getPassword(),
)
return `${signature}-${time}`
} catch {
throw new Error("User denied the signature.")
}
}
/**
* Returns the address of signed token.
* @param {string} token Token.
* @return {Promise<string>} Signer address.
*/
public async check(token: string): Promise<string> {
const expiration = 30 * 24 * 60 * 60 * 1000 // 30 days
const [signature, timestamp] = token.split('-')
const message = `${defaultAuthMessage}\n${timestamp}`
if ((+timestamp + expiration) < Date.now()) {
return `0x${"0".repeat(40)}`
}
return this.web3.utils.toChecksumAddress(
await this.ocean.utils.signature.verifyText(message, signature)
)
}
/**
* Generates and stores the token for a account.
* @param {Account} account Signer account.
*/
public async store(account: Account) {
const token = await this.get(account)
this.writeToken(account.getId(), token)
}
/**
* Returns a stored token.
* @param {Account} account Signer account.
*/
public async restore(account: Account): Promise<string> {
const token = this.readToken(account.getId())
if (!token) {
return
}
const signer = await this.check(token)
if (signer.toLowerCase() !== account.getId().toLowerCase()) {
return
}
return token
}
/**
* Returns if the token is stored and is valid.
* @param {Account} account Signer account.
* @return {Promise<boolean>} Is stored and valid.
*/
public async isStored(account: Account): Promise<boolean> {
return !!this.restore(account)
}
private writeToken(address: string, token: string) {
const storedTokens = localStorage.getItem(localStorageKey)
const tokens = storedTokens ? JSON.parse(storedTokens) : {}
localStorage.setItem(localStorageKey, JSON.stringify({
...tokens,
[address]: token,
}))
}
private readToken(address: string) {
const storedTokens = localStorage.getItem(localStorageKey)
const tokens = storedTokens ? JSON.parse(storedTokens) : {}
return tokens[address]
}
}

View File

@ -1,4 +1,3 @@
import Keeper from "../keeper/Keeper"
import Account from "./Account"
import { Instantiable, InstantiableConfig } from "../Instantiable.abstract"

View File

@ -1,5 +1,3 @@
import * as Web3 from "web3"
import { Instantiable, InstantiableConfig } from "../../Instantiable.abstract"
export class SignatureUtils extends Instantiable {

View File

@ -0,0 +1,104 @@
import { assert, expect, spy, use } from "chai"
import * as spies from "chai-spies"
import config from "../config"
import Account from "../../src/ocean/Account"
import { Ocean } from "../../src/ocean/Ocean"
import { OceanAuth } from "../../src/ocean/OceanAuth"
use(spies)
describe("OceanAuth", () => {
let oceanAuth: OceanAuth
let account: Account
before(async () => {
const ocean = await Ocean.getInstance(config)
oceanAuth = ocean.auth
account = (await ocean.accounts.list())[0]
})
afterEach(() => {
spy.restore()
})
describe("#get()", () => {
it("should return the token for a account", async () => {
const token = await oceanAuth.get(account)
assert.match(token, /^0x[a-f0-9]{130}-[0-9]{0,14}/i)
})
})
// Not valid using providers without support for `personal_ecRecover`
xdescribe("#check()", () => {
it("should return the account of a signature", async () => {
const token = await oceanAuth.get(account)
const address = await oceanAuth.check(token)
assert.equal(address, account.getId())
})
it("should return empty address if the token is expired", async () => {
const token = [
"0x0cfe59ce5c35461728b4126431096e4e021a842ca1f679532c537be5f895a3607e498",
"f2cc22f787f9c7c8a967c346d717ef50ccb9f0af418d87a86dad899e6d61b-1234567890",
].join("")
const address = await oceanAuth.check(token)
assert.equal(address, `0x${"0".repeat(40)}`)
})
})
describe("#store()", () => {
it("should sign and store the token", async () => {
const writeTokenSpy = spy.on(oceanAuth as any, 'writeToken', () => {})
await oceanAuth.store(account)
expect(writeTokenSpy).to.has.been.called()
})
})
describe("#restore()", () => {
it("should return a stored token", async () => {
spy.on(oceanAuth as any, 'readToken', () => 'token')
spy.on(oceanAuth as any, 'check', () => account.getId())
const token = await oceanAuth.restore(account)
assert.equal(token, 'token')
})
it("should not return values if there is any error", async () => {
spy.on(oceanAuth as any, 'readToken', () => 'token')
spy.on(oceanAuth as any, 'check', () => '0x...')
const token = await oceanAuth.restore(account)
assert.isUndefined(token)
})
})
describe("#isStored()", () => {
it("should know if the token is stored", async () => {
spy.on(oceanAuth as any, 'restore', () => account.getId())
const isStored = await oceanAuth.isStored(account)
assert.isTrue(isStored)
})
it("should know if the token is not stored", async () => {
spy.on(oceanAuth as any, 'restore', () => undefined)
const isStored = await oceanAuth.isStored(account)
assert.isFalse(isStored)
})
})
})