-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Implement SDK generator engines #14654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: tools/pm-18793/port-credential-generator-service-to-providers
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { | ||
BitwardenClient, | ||
PassphraseGeneratorRequest, | ||
PasswordGeneratorRequest, | ||
} from "@bitwarden/sdk-internal"; | ||
|
||
import { | ||
CredentialGenerator, | ||
GenerateRequest, | ||
GeneratedCredential, | ||
PassphraseGenerationOptions, | ||
PasswordGenerationOptions, | ||
} from "../types"; | ||
|
||
/** Generation algorithms that produce randomized secrets */ | ||
export class SdkPasswordRandomizer | ||
implements | ||
CredentialGenerator<PassphraseGenerationOptions>, | ||
CredentialGenerator<PasswordGenerationOptions> | ||
{ | ||
/** Instantiates the password randomizer | ||
* @param randomizer data source for random data | ||
*/ | ||
constructor(private client: BitwardenClient) {} | ||
|
||
generate( | ||
request: GenerateRequest, | ||
settings: PasswordGenerationOptions, | ||
): Promise<GeneratedCredential>; | ||
generate( | ||
request: GenerateRequest, | ||
settings: PassphraseGenerationOptions, | ||
): Promise<GeneratedCredential>; | ||
async generate( | ||
request: GenerateRequest, | ||
settings: PasswordGenerationOptions | PassphraseGenerationOptions, | ||
) { | ||
if (isPasswordGenerationOptions(settings)) { | ||
const password = await this.client.generator().password(convertPasswordRequest(settings)); | ||
|
||
return new GeneratedCredential( | ||
password, | ||
"password", | ||
Date.now(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ๏ธ This should use an injected date method; it shouldn't call |
||
request.source, | ||
request.website, | ||
); | ||
} else if (isPassphraseGenerationOptions(settings)) { | ||
const passphrase = await this.client | ||
.generator() | ||
.passphrase(convertPassphraseRequest(settings)); | ||
|
||
return new GeneratedCredential( | ||
passphrase, | ||
"password", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ๏ธ This should be |
||
Date.now(), | ||
request.source, | ||
request.website, | ||
); | ||
} | ||
|
||
throw new Error("Invalid settings received by generator."); | ||
} | ||
} | ||
|
||
function convertPasswordRequest(settings: PasswordGenerationOptions): PasswordGeneratorRequest { | ||
return { | ||
lowercase: settings.lowercase, | ||
uppercase: settings.uppercase, | ||
numbers: settings.number, | ||
special: settings.special, | ||
length: settings.length, | ||
avoidAmbiguous: settings.ambiguous, | ||
minLowercase: settings.minLowercase, | ||
minUppercase: settings.minUppercase, | ||
minNumber: settings.minNumber, | ||
minSpecial: settings.minSpecial, | ||
}; | ||
} | ||
|
||
function convertPassphraseRequest( | ||
settings: PassphraseGenerationOptions, | ||
): PassphraseGeneratorRequest { | ||
return { | ||
numWords: settings.numWords, | ||
wordSeparator: settings.wordSeparator, | ||
capitalize: settings.capitalize, | ||
includeNumber: settings.includeNumber, | ||
}; | ||
} | ||
audreyality marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function isPasswordGenerationOptions(settings: any): settings is PasswordGenerationOptions { | ||
return "length" in (settings ?? {}); | ||
} | ||
|
||
function isPassphraseGenerationOptions(settings: any): settings is PassphraseGenerationOptions { | ||
return "numWords" in (settings ?? {}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,12 @@ export const Algorithm = Object.freeze({ | |
/** A password composed of random words from the EFF word list */ | ||
passphrase: "passphrase", | ||
|
||
/** A password composed of random characters, retrieved from SDK */ | ||
sdkPassword: "sdkpassword", | ||
|
||
/** A password composed of random words from the EFF word list, retrieved from SDK */ | ||
sdkPassphrase: "sdkpassphrase", | ||
|
||
Comment on lines
+11
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ๏ธ Make sure the casing is consistent between the name and value |
||
/** A username composed of words from the EFF word list */ | ||
username: "username", | ||
|
||
|
@@ -38,7 +44,12 @@ export const Profile = Object.freeze({ | |
/** Credential generation algorithms grouped by purpose. */ | ||
export const AlgorithmsByType = deepFreeze({ | ||
/** Algorithms that produce passwords */ | ||
[Type.password]: [Algorithm.password, Algorithm.passphrase] as const, | ||
[Type.password]: [ | ||
Algorithm.password, | ||
Algorithm.passphrase, | ||
Algorithm.sdkPassword, | ||
Algorithm.sdkPassphrase, | ||
] as const, | ||
|
||
/** Algorithms that produce usernames */ | ||
[Type.username]: [Algorithm.username] as const, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { PolicyType } from "@bitwarden/common/admin-console/enums"; | ||
import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; | ||
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; | ||
import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; | ||
|
||
import { SdkPasswordRandomizer } from "../../engine"; | ||
import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; | ||
import { GeneratorDependencyProvider } from "../../providers"; | ||
import { CredentialGenerator, PassphraseGenerationOptions } from "../../types"; | ||
import { Algorithm, Profile, Type } from "../data"; | ||
import { GeneratorMetadata } from "../generator-metadata"; | ||
|
||
const sdkPassphrase: GeneratorMetadata<PassphraseGenerationOptions> = { | ||
id: Algorithm.sdkPassphrase, | ||
type: Type.password, | ||
weight: 130, | ||
i18nKeys: { | ||
name: "passphrase", | ||
credentialType: "passphrase", | ||
generateCredential: "generatePassphrase", | ||
credentialGenerated: "passphraseGenerated", | ||
copyCredential: "copyPassphrase", | ||
useCredential: "useThisPassphrase", | ||
}, | ||
capabilities: { | ||
autogenerate: false, | ||
fields: [], | ||
}, | ||
engine: { | ||
create( | ||
dependencies: GeneratorDependencyProvider, | ||
): CredentialGenerator<PassphraseGenerationOptions> { | ||
return new SdkPasswordRandomizer(dependencies.sdk); | ||
}, | ||
}, | ||
profiles: { | ||
[Profile.account]: { | ||
type: "core", | ||
storage: { | ||
key: "passphraseGeneratorSettings", | ||
target: "object", | ||
format: "plain", | ||
classifier: new PublicClassifier<PassphraseGenerationOptions>([ | ||
"numWords", | ||
"wordSeparator", | ||
"capitalize", | ||
"includeNumber", | ||
]), | ||
state: GENERATOR_DISK, | ||
initial: { | ||
numWords: 6, | ||
wordSeparator: "-", | ||
capitalize: false, | ||
includeNumber: false, | ||
}, | ||
options: { | ||
deserializer(value) { | ||
return value; | ||
}, | ||
clearOn: ["logout"], | ||
}, | ||
} satisfies ObjectKey<PassphraseGenerationOptions>, | ||
constraints: { | ||
type: PolicyType.PasswordGenerator, | ||
default: { | ||
wordSeparator: { maxLength: 1 }, | ||
numWords: { | ||
min: 3, | ||
max: 20, | ||
recommendation: 6, | ||
}, | ||
}, | ||
create(policies, context) { | ||
const initial = { | ||
minNumberWords: 0, | ||
capitalize: false, | ||
includeNumber: false, | ||
}; | ||
const policy = policies.reduce(passphraseLeastPrivilege, initial); | ||
const constraints = new PassphrasePolicyConstraints(policy, context.defaultConstraints); | ||
return constraints; | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export default sdkPassphrase; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { PolicyType } from "@bitwarden/common/admin-console/enums"; | ||
import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; | ||
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; | ||
import { deepFreeze } from "@bitwarden/common/tools/util"; | ||
|
||
import { SdkPasswordRandomizer } from "../../engine"; | ||
import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; | ||
import { GeneratorDependencyProvider } from "../../providers"; | ||
import { CredentialGenerator, PasswordGeneratorSettings } from "../../types"; | ||
import { Algorithm, Profile, Type } from "../data"; | ||
import { GeneratorMetadata } from "../generator-metadata"; | ||
|
||
const sdkPassword: GeneratorMetadata<PasswordGeneratorSettings> = deepFreeze({ | ||
id: Algorithm.sdkPassword, | ||
type: Type.password, | ||
weight: 120, | ||
i18nKeys: { | ||
name: "password", | ||
generateCredential: "generatePassword", | ||
credentialGenerated: "passwordGenerated", | ||
credentialType: "password", | ||
copyCredential: "copyPassword", | ||
useCredential: "useThisPassword", | ||
}, | ||
capabilities: { | ||
autogenerate: true, | ||
fields: [], | ||
}, | ||
engine: { | ||
create( | ||
dependencies: GeneratorDependencyProvider, | ||
): CredentialGenerator<PasswordGeneratorSettings> { | ||
return new SdkPasswordRandomizer(dependencies.sdk); | ||
}, | ||
}, | ||
profiles: { | ||
[Profile.account]: { | ||
type: "core", | ||
storage: { | ||
key: "passwordGeneratorSettings", | ||
target: "object", | ||
format: "plain", | ||
classifier: new PublicClassifier<PasswordGeneratorSettings>([ | ||
"length", | ||
"ambiguous", | ||
"uppercase", | ||
"minUppercase", | ||
"lowercase", | ||
"minLowercase", | ||
"number", | ||
"minNumber", | ||
"special", | ||
"minSpecial", | ||
]), | ||
state: GENERATOR_DISK, | ||
initial: { | ||
length: 14, | ||
ambiguous: true, | ||
uppercase: true, | ||
minUppercase: 1, | ||
lowercase: true, | ||
minLowercase: 1, | ||
number: true, | ||
minNumber: 1, | ||
special: false, | ||
minSpecial: 0, | ||
}, | ||
options: { | ||
deserializer(value) { | ||
return value; | ||
}, | ||
clearOn: ["logout"], | ||
}, | ||
}, | ||
constraints: { | ||
type: PolicyType.PasswordGenerator, | ||
default: { | ||
length: { | ||
min: 5, | ||
max: 128, | ||
recommendation: 14, | ||
}, | ||
minNumber: { | ||
min: 0, | ||
max: 9, | ||
}, | ||
minSpecial: { | ||
min: 0, | ||
max: 9, | ||
}, | ||
}, | ||
create(policies, context) { | ||
const initial = { | ||
minLength: 0, | ||
useUppercase: false, | ||
useLowercase: false, | ||
useNumbers: false, | ||
numberCount: 0, | ||
useSpecial: false, | ||
specialCount: 0, | ||
}; | ||
const policy = policies.reduce(passwordLeastPrivilege, initial); | ||
const constraints = new DynamicPasswordPolicyConstraints( | ||
policy, | ||
context.defaultConstraints, | ||
); | ||
return constraints; | ||
}, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
export default sdkPassword; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
โ๏ธ This should be
Type.password
, not a hard-coded string.