diff options
Diffstat (limited to 'packages/anastasis-core/src')
-rw-r--r-- | packages/anastasis-core/src/anastasis-data.ts | 17 | ||||
-rw-r--r-- | packages/anastasis-core/src/crypto.ts | 2 | ||||
-rw-r--r-- | packages/anastasis-core/src/index.ts | 124 | ||||
-rw-r--r-- | packages/anastasis-core/src/policy-suggestion.test.ts | 44 | ||||
-rw-r--r-- | packages/anastasis-core/src/policy-suggestion.ts | 25 | ||||
-rw-r--r-- | packages/anastasis-core/src/reducer-types.ts | 13 |
6 files changed, 209 insertions, 16 deletions
diff --git a/packages/anastasis-core/src/anastasis-data.ts b/packages/anastasis-core/src/anastasis-data.ts index 4946e9dfd..c67883a2e 100644 --- a/packages/anastasis-core/src/anastasis-data.ts +++ b/packages/anastasis-core/src/anastasis-data.ts @@ -1,6 +1,7 @@ // This file is auto-generated, do not modify. // Generated from v0.2.0-4-g61ea83c on Tue, 05 Oct 2021 10:40:32 +0200 // To re-generate, run contrib/gen-ts.sh from the main anastasis code base. +// XXX: Modified for demo, allowing demo providers for EUR export const anastasisData = { providersList: { @@ -16,6 +17,22 @@ export const anastasisData = { currency: "KUDOS", }, { + url: "https://anastasis.demo.taler.net/", + currency: "EUR", + }, + { + url: "https://kudos.demo.anastasis.lu/", + currency: "EUR", + }, + { + url: "https://anastasis.demo.taler.net/", + currency: "CHF", + }, + { + url: "https://kudos.demo.anastasis.lu/", + currency: "CHF", + }, + { url: "http://localhost:8086/", currency: "TESTKUDOS", }, diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts index 206d9eca8..75bd4b323 100644 --- a/packages/anastasis-core/src/crypto.ts +++ b/packages/anastasis-core/src/crypto.ts @@ -11,8 +11,6 @@ import { stringToBytes, secretbox_open, hash, - Logger, - j2s, } from "@gnu-taler/taler-util"; import { argon2id } from "hash-wasm"; diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 362ac3317..15e1e5d97 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -65,6 +65,8 @@ import { ActionArgsChangeVersion, TruthMetaData, ActionArgsUpdatePolicy, + ActionArgsAddProvider, + ActionArgsDeleteProvider, } from "./reducer-types.js"; import fetchPonyfill from "fetch-ponyfill"; import { @@ -109,13 +111,23 @@ export * from "./challenge-feedback-types.js"; const logger = new Logger("anastasis-core:index.ts"); -function getContinents(): ContinentInfo[] { +function getContinents( + opts: { requireProvider?: boolean } = {}, +): ContinentInfo[] { + const currenciesWithProvider = new Set<string>(); + anastasisData.providersList.anastasis_provider.forEach((x) => { + currenciesWithProvider.add(x.currency); + }); const continentSet = new Set<string>(); const continents: ContinentInfo[] = []; for (const country of anastasisData.countriesList.countries) { if (continentSet.has(country.continent)) { continue; } + if (opts.requireProvider && !currenciesWithProvider.has(country.currency)) { + // Country's currency doesn't have any providers => skip + continue; + } continentSet.add(country.continent); continents.push({ ...{ name_i18n: country.continent_i18n }, @@ -148,9 +160,18 @@ export class ReducerError extends Error { * Get countries for a continent, abort with ReducerError * exception when continent doesn't exist. */ -function getCountries(continent: string): CountryInfo[] { +function getCountries( + continent: string, + opts: { requireProvider?: boolean } = {}, +): CountryInfo[] { + const currenciesWithProvider = new Set<string>(); + anastasisData.providersList.anastasis_provider.forEach((x) => { + currenciesWithProvider.add(x.currency); + }); const countries = anastasisData.countriesList.countries.filter( - (x) => x.continent === continent, + (x) => + x.continent === continent && + (!opts.requireProvider || currenciesWithProvider.has(x.currency)), ); if (countries.length <= 0) { throw new ReducerError({ @@ -164,14 +185,18 @@ function getCountries(continent: string): CountryInfo[] { export async function getBackupStartState(): Promise<ReducerStateBackup> { return { backup_state: BackupStates.ContinentSelecting, - continents: getContinents(), + continents: getContinents({ + requireProvider: true, + }), }; } export async function getRecoveryStartState(): Promise<ReducerStateRecovery> { return { recovery_state: RecoveryStates.ContinentSelecting, - continents: getContinents(), + continents: getContinents({ + requireProvider: true, + }), }; } @@ -952,6 +977,21 @@ async function requestTruth( } if (resp.status === HttpStatusCode.Forbidden) { + const body = await resp.json(); + if ( + body.code === TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED + ) { + return { + ...state, + recovery_state: RecoveryStates.ChallengeSolving, + challenge_feedback: { + ...state.challenge_feedback, + [truth.uuid]: { + state: ChallengeFeedbackStatus.Pending, + }, + }, + }; + } return { ...state, recovery_state: RecoveryStates.ChallengeSolving, @@ -959,7 +999,7 @@ async function requestTruth( ...state.challenge_feedback, [truth.uuid]: { state: ChallengeFeedbackStatus.Message, - message: "Challenge should be solved", + message: body.hint ?? "Challenge should be solved", }, }, }; @@ -1022,9 +1062,15 @@ async function recoveryEnterUserAttributes( args: ActionArgsEnterUserAttributes, ): Promise<ReducerStateRecovery | ReducerStateError> { // FIXME: validate attributes + const providerUrls = Object.keys(state.authentication_providers ?? {}); + const newProviders = state.authentication_providers ?? {}; + for (const url of providerUrls) { + newProviders[url] = await getProviderInfo(url); + } const st: ReducerStateRecovery = { ...state, identity_attributes: args.identity_attributes, + authentication_providers: newProviders, }; return downloadPolicy(st); } @@ -1058,7 +1104,9 @@ async function backupSelectContinent( state: ReducerStateBackup, args: ActionArgsSelectContinent, ): Promise<ReducerStateBackup | ReducerStateError> { - const countries = getCountries(args.continent); + const countries = getCountries(args.continent, { + requireProvider: true, + }); if (countries.length <= 0) { return { code: TalerErrorCode.ANASTASIS_REDUCER_INPUT_INVALID, @@ -1077,7 +1125,9 @@ async function recoverySelectContinent( state: ReducerStateRecovery, args: ActionArgsSelectContinent, ): Promise<ReducerStateRecovery | ReducerStateError> { - const countries = getCountries(args.continent); + const countries = getCountries(args.continent, { + requireProvider: true, + }); return { ...state, recovery_state: RecoveryStates.CountrySelecting, @@ -1132,6 +1182,60 @@ function transitionRecoveryJump( }; } +//FIXME: doest the same that addProviderRecovery, but type are not generic enough +async function addProviderBackup( + state: ReducerStateBackup, + args: ActionArgsAddProvider, +): Promise<ReducerStateBackup> { + const info = await getProviderInfo(args.provider_url) + return { + ...state, + authentication_providers: { + ...(state.authentication_providers ?? {}), + [args.provider_url]: info, + }, + }; +} + +//FIXME: doest the same that deleteProviderRecovery, but type are not generic enough +async function deleteProviderBackup( + state: ReducerStateBackup, + args: ActionArgsDeleteProvider, +): Promise<ReducerStateBackup> { + const authentication_providers = {... state.authentication_providers ?? {} } + delete authentication_providers[args.provider_url] + return { + ...state, + authentication_providers, + }; +} + +async function addProviderRecovery( + state: ReducerStateRecovery, + args: ActionArgsAddProvider, +): Promise<ReducerStateRecovery> { + const info = await getProviderInfo(args.provider_url) + return { + ...state, + authentication_providers: { + ...(state.authentication_providers ?? {}), + [args.provider_url]: info, + }, + }; +} + +async function deleteProviderRecovery( + state: ReducerStateRecovery, + args: ActionArgsDeleteProvider, +): Promise<ReducerStateRecovery> { + const authentication_providers = {... state.authentication_providers ?? {} } + delete authentication_providers[args.provider_url] + return { + ...state, + authentication_providers, + }; +} + async function addAuthentication( state: ReducerStateBackup, args: ActionArgsAddAuthentication, @@ -1366,6 +1470,8 @@ const backupTransitions: Record< ...transitionBackupJump("back", BackupStates.UserAttributesCollecting), ...transition("add_authentication", codecForAny(), addAuthentication), ...transition("delete_authentication", codecForAny(), deleteAuthentication), + ...transition("add_provider", codecForAny(), addProviderBackup), + ...transition("delete_provider", codecForAny(), deleteProviderBackup), ...transition("next", codecForAny(), nextFromAuthenticationsEditing), }, [BackupStates.PoliciesReviewing]: { @@ -1434,6 +1540,8 @@ const recoveryTransitions: Record< [RecoveryStates.SecretSelecting]: { ...transitionRecoveryJump("back", RecoveryStates.UserAttributesCollecting), ...transitionRecoveryJump("next", RecoveryStates.ChallengeSelecting), + ...transition("add_provider", codecForAny(), addProviderRecovery), + ...transition("delete_provider", codecForAny(), deleteProviderRecovery), ...transition( "change_version", codecForActionArgsChangeVersion(), diff --git a/packages/anastasis-core/src/policy-suggestion.test.ts b/packages/anastasis-core/src/policy-suggestion.test.ts new file mode 100644 index 000000000..6370825da --- /dev/null +++ b/packages/anastasis-core/src/policy-suggestion.test.ts @@ -0,0 +1,44 @@ +import { j2s } from "@gnu-taler/taler-util"; +import test from "ava"; +import { ProviderInfo, suggestPolicies } from "./policy-suggestion.js"; + +test("policy suggestion", async (t) => { + const methods = [ + { + challenge: "XXX", + instructions: "SMS to 123", + type: "sms", + }, + { + challenge: "XXX", + instructions: "What is the meaning of life?", + type: "question", + }, + { + challenge: "XXX", + instructions: "email to foo@bar.com", + type: "email", + }, + ]; + const providers: ProviderInfo[] = [ + { + methodCost: { + sms: "KUDOS:1", + }, + url: "prov1", + }, + { + methodCost: { + question: "KUDOS:1", + }, + url: "prov2", + }, + ]; + const res1 = suggestPolicies(methods, providers); + t.assert(res1.policies.length === 1); + const res2 = suggestPolicies([...methods].reverse(), providers); + t.assert(res2.policies.length === 1); + + const res3 = suggestPolicies(methods, [...providers].reverse()); + t.assert(res3.policies.length === 1); +}); diff --git a/packages/anastasis-core/src/policy-suggestion.ts b/packages/anastasis-core/src/policy-suggestion.ts index 7eb6c21cc..2c25caaa4 100644 --- a/packages/anastasis-core/src/policy-suggestion.ts +++ b/packages/anastasis-core/src/policy-suggestion.ts @@ -84,9 +84,16 @@ function assignProviders( for (const provSel of providerSelections) { // First, check if selection is even possible with the methods offered let possible = true; - for (const methIndex in provSel) { - const provIndex = provSel[methIndex]; + for (const methSelIndex in provSel) { + const provIndex = provSel[methSelIndex]; + if (typeof provIndex !== "number") { + throw Error("invariant failed"); + } + const methIndex = methodSelection[methSelIndex]; const meth = methods[methIndex]; + if (!meth) { + throw Error("invariant failed"); + } const prov = providers[provIndex]; if (!prov.methodCost[meth.type]) { possible = false; @@ -96,7 +103,6 @@ function assignProviders( if (!possible) { continue; } - // Evaluate diversity, always prefer policies // that increase diversity. const providerSet = new Set<string>(); @@ -163,10 +169,19 @@ function assignProviders( /** * A provider selection maps a method selection index to a provider index. + * + * I.e. "PSEL[i] = x" means that provider with index "x" should be used + * for method with index "MSEL[i]" */ type ProviderSelection = number[]; /** + * A method selection "MSEL[j] = y" means that policy method j + * should use method y. + */ +type MethodSelection = number[]; + +/** * Compute provider mappings. * Enumerates all n-combinations with repetition of m providers. */ @@ -184,7 +199,7 @@ function enumerateProviderMappings( } for (let j = start; j < m; j++) { a[i] = j; - sel(i + 1, j); + sel(i + 1, 0); if (limit && selections.length >= limit) { break; } @@ -199,8 +214,6 @@ interface PolicySelectionResult { policy_providers: PolicyProvider[]; } -type MethodSelection = number[]; - /** * Compute method selections. * Enumerates all n-combinations without repetition of m methods. diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts index 0f64be4eb..3e6d6c852 100644 --- a/packages/anastasis-core/src/reducer-types.ts +++ b/packages/anastasis-core/src/reducer-types.ts @@ -50,6 +50,11 @@ export interface SuccessDetails { export interface CoreSecret { mime: string; value: string; + /** + * Filename, only set if the secret comes from + * a file. Should be set unless the mime type is "text/plain"; + */ + filename?: string; } export interface ReducerStateBackup { @@ -329,6 +334,14 @@ export const codecForActionArgsEnterUserAttributes = () => .property("identity_attributes", codecForAny()) .build("ActionArgsEnterUserAttributes"); +export interface ActionArgsAddProvider { + provider_url: string; +} + +export interface ActionArgsDeleteProvider { + provider_url: string; +} + export interface ActionArgsAddAuthentication { authentication_method: { type: string; |