@gasboost/auth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +484 -0
- package/dist/AppsScriptAuth.d.ts +51 -0
- package/dist/AppsScriptAuth.js +77 -0
- package/dist/AuthPattern.d.ts +5 -0
- package/dist/AuthPattern.js +4 -0
- package/dist/api/AppsScriptAuthPassword.d.ts +30 -0
- package/dist/api/AppsScriptAuthPassword.js +64 -0
- package/dist/api/AppsScriptAuthSession.d.ts +10 -0
- package/dist/api/AppsScriptAuthSession.js +17 -0
- package/dist/api/AppsScriptAuthSignIn.d.ts +20 -0
- package/dist/api/AppsScriptAuthSignIn.js +34 -0
- package/dist/api/AppsScriptAuthSignOut.d.ts +7 -0
- package/dist/api/AppsScriptAuthSignOut.js +9 -0
- package/dist/api/AppsScriptAuthSignUp.d.ts +20 -0
- package/dist/api/AppsScriptAuthSignUp.js +35 -0
- package/dist/api/SignIn.d.ts +19 -0
- package/dist/api/SignIn.js +26 -0
- package/dist/api/SignUp.d.ts +20 -0
- package/dist/api/SignUp.js +25 -0
- package/dist/authentication/AppsScriptAuthentication.d.ts +24 -0
- package/dist/authentication/AppsScriptAuthentication.js +47 -0
- package/dist/authentication/Authentication.d.ts +4 -0
- package/dist/authentication/Authentication.js +1 -0
- package/dist/authentication/EmailPasswordAuthentication.d.ts +42 -0
- package/dist/authentication/EmailPasswordAuthentication.js +75 -0
- package/dist/domain/Account.d.ts +11 -0
- package/dist/domain/Account.js +10 -0
- package/dist/domain/Errors.d.ts +3 -0
- package/dist/domain/Errors.js +5 -0
- package/dist/domain/Password.d.ts +15 -0
- package/dist/domain/Password.js +54 -0
- package/dist/domain/PasswordCredential.d.ts +25 -0
- package/dist/domain/PasswordCredential.js +57 -0
- package/dist/domain/PasswordReset.d.ts +25 -0
- package/dist/domain/PasswordReset.js +44 -0
- package/dist/domain/PasswordResetToken.d.ts +6 -0
- package/dist/domain/PasswordResetToken.js +17 -0
- package/dist/domain/Session.d.ts +14 -0
- package/dist/domain/Session.js +15 -0
- package/dist/domain/User.d.ts +13 -0
- package/dist/domain/User.js +13 -0
- package/dist/factory/sessionStorageFactory.d.ts +6 -0
- package/dist/factory/sessionStorageFactory.js +11 -0
- package/dist/identity/AppsScriptIdentity.d.ts +8 -0
- package/dist/identity/AppsScriptIdentity.js +8 -0
- package/dist/identity/EmailPasswordIdentity.d.ts +13 -0
- package/dist/identity/EmailPasswordIdentity.js +19 -0
- package/dist/identity/ProviderIdentity.d.ts +5 -0
- package/dist/identity/ProviderIdentity.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/registration/AppsScriptRegistration.d.ts +13 -0
- package/dist/registration/AppsScriptRegistration.js +46 -0
- package/dist/registration/EmailPasswordRegistration.d.ts +16 -0
- package/dist/registration/EmailPasswordRegistration.js +41 -0
- package/dist/registration/Registration.d.ts +4 -0
- package/dist/registration/Registration.js +1 -0
- package/dist/schema/AuthSchema.d.ts +64 -0
- package/dist/schema/AuthSchema.js +35 -0
- package/dist/storage/AppsScriptAuthRepository.d.ts +17 -0
- package/dist/storage/AppsScriptAuthRepository.js +1 -0
- package/dist/storage/AppsScriptCacheSessionStorage.d.ts +10 -0
- package/dist/storage/AppsScriptCacheSessionStorage.js +30 -0
- package/dist/storage/AppsScriptPropertiesSessionStorage.d.ts +11 -0
- package/dist/storage/AppsScriptPropertiesSessionStorage.js +51 -0
- package/dist/storage/AppsScriptSessionStorage.d.ts +7 -0
- package/dist/storage/AppsScriptSessionStorage.js +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SessionId } from "../domain/Session";
|
|
2
|
+
import { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
|
|
3
|
+
export declare class AppsScriptAuthSession {
|
|
4
|
+
private sessionStorage;
|
|
5
|
+
constructor({ sessionStorage, }: {
|
|
6
|
+
sessionStorage: AppsScriptSessionStorage;
|
|
7
|
+
});
|
|
8
|
+
get(sessionId: SessionId): Promise<import("../domain/Session").Session | null>;
|
|
9
|
+
cleanup(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class AppsScriptAuthSession {
|
|
2
|
+
sessionStorage;
|
|
3
|
+
constructor({ sessionStorage, }) {
|
|
4
|
+
this.sessionStorage = sessionStorage;
|
|
5
|
+
}
|
|
6
|
+
async get(sessionId) {
|
|
7
|
+
const session = await this.sessionStorage.get(sessionId);
|
|
8
|
+
if (session && session.isExpired(new Date())) {
|
|
9
|
+
await this.sessionStorage.delete(session.id);
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return session;
|
|
13
|
+
}
|
|
14
|
+
async cleanup() {
|
|
15
|
+
await this.sessionStorage.cleanupExpired();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AppsScriptAuthenticationConfig } from "../authentication/AppsScriptAuthentication";
|
|
2
|
+
import { type AppsScriptCredential } from "../authentication/AppsScriptAuthentication";
|
|
3
|
+
import type { EmailPasswordAuthConfig } from "../authentication/EmailPasswordAuthentication";
|
|
4
|
+
import { type EmailPasswordCredential } from "../authentication/EmailPasswordAuthentication";
|
|
5
|
+
import { AppsScriptAuthRepository } from "../storage/AppsScriptAuthRepository";
|
|
6
|
+
import { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
|
|
7
|
+
import { SignIn } from "./SignIn";
|
|
8
|
+
export declare class AppsScriptAuthSignIn {
|
|
9
|
+
email: SignIn<EmailPasswordCredential>["execute"];
|
|
10
|
+
appsScript: SignIn<AppsScriptCredential>["execute"];
|
|
11
|
+
constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, session, utilities, }: {
|
|
12
|
+
sessionStorage: AppsScriptSessionStorage;
|
|
13
|
+
repository: AppsScriptAuthRepository;
|
|
14
|
+
emailPassword?: EmailPasswordAuthConfig;
|
|
15
|
+
appsScript?: AppsScriptAuthenticationConfig;
|
|
16
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
17
|
+
session: GoogleAppsScript.Base.Session;
|
|
18
|
+
expiresIn: number;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AppsScriptAuthentication, } from "../authentication/AppsScriptAuthentication";
|
|
2
|
+
import { EmailPasswordAuthentication, } from "../authentication/EmailPasswordAuthentication";
|
|
3
|
+
import { SignIn } from "./SignIn";
|
|
4
|
+
export class AppsScriptAuthSignIn {
|
|
5
|
+
email;
|
|
6
|
+
appsScript;
|
|
7
|
+
constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, session, utilities, }) {
|
|
8
|
+
this.email = async (credential) => {
|
|
9
|
+
if (!emailPassword) {
|
|
10
|
+
throw new Error("Email and password authentication is disabled");
|
|
11
|
+
}
|
|
12
|
+
emailPassword.ensureSignInEnabled();
|
|
13
|
+
const emailSignIn = new SignIn({
|
|
14
|
+
sessionStorage,
|
|
15
|
+
authentication: new EmailPasswordAuthentication(repository, utilities, emailPassword),
|
|
16
|
+
utilities,
|
|
17
|
+
expiresIn,
|
|
18
|
+
});
|
|
19
|
+
return emailSignIn.execute(credential);
|
|
20
|
+
};
|
|
21
|
+
this.appsScript = async (credential) => {
|
|
22
|
+
if (!appsScript) {
|
|
23
|
+
throw new Error("Apps Script authentication is disabled");
|
|
24
|
+
}
|
|
25
|
+
appsScript.ensureSignInEnabled();
|
|
26
|
+
return new SignIn({
|
|
27
|
+
sessionStorage,
|
|
28
|
+
authentication: new AppsScriptAuthentication(repository, session),
|
|
29
|
+
utilities,
|
|
30
|
+
expiresIn,
|
|
31
|
+
}).execute(credential);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionId } from "../domain/Session";
|
|
2
|
+
import type { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
|
|
3
|
+
export declare class AppsScriptAuthSignOut {
|
|
4
|
+
private readonly sessionStorage;
|
|
5
|
+
constructor(sessionStorage: AppsScriptSessionStorage);
|
|
6
|
+
execute(sessionId: SessionId): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AppsScriptAuthenticationConfig } from "../authentication/AppsScriptAuthentication";
|
|
2
|
+
import type { EmailPasswordAuthConfig } from "../authentication/EmailPasswordAuthentication";
|
|
3
|
+
import { type AppsScriptRegistrationInput } from "../registration/AppsScriptRegistration";
|
|
4
|
+
import { type EmailPasswordRegistrationInput } from "../registration/EmailPasswordRegistration";
|
|
5
|
+
import type { AppsScriptAuthRepository } from "../storage/AppsScriptAuthRepository";
|
|
6
|
+
import type { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
|
|
7
|
+
import { SignUp } from "./SignUp";
|
|
8
|
+
export declare class AppsScriptAuthSignUp {
|
|
9
|
+
email: SignUp<EmailPasswordRegistrationInput>["execute"];
|
|
10
|
+
appsScript: SignUp<AppsScriptRegistrationInput>["execute"];
|
|
11
|
+
constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, utilities, session, }: {
|
|
12
|
+
sessionStorage: AppsScriptSessionStorage;
|
|
13
|
+
expiresIn: number;
|
|
14
|
+
repository: AppsScriptAuthRepository;
|
|
15
|
+
emailPassword?: EmailPasswordAuthConfig;
|
|
16
|
+
appsScript?: AppsScriptAuthenticationConfig;
|
|
17
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
18
|
+
session: GoogleAppsScript.Base.Session;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AppsScriptRegistration, } from "../registration/AppsScriptRegistration";
|
|
2
|
+
import { EmailPasswordRegistration, } from "../registration/EmailPasswordRegistration";
|
|
3
|
+
import { SignUp } from "./SignUp";
|
|
4
|
+
export class AppsScriptAuthSignUp {
|
|
5
|
+
email;
|
|
6
|
+
appsScript;
|
|
7
|
+
constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, utilities, session, }) {
|
|
8
|
+
this.email = async (input) => {
|
|
9
|
+
if (!emailPassword) {
|
|
10
|
+
throw new Error("Email and password authentication is disabled");
|
|
11
|
+
}
|
|
12
|
+
emailPassword.ensureSignUpEnabled();
|
|
13
|
+
const emailSignUp = new SignUp({
|
|
14
|
+
sessionStorage,
|
|
15
|
+
expiresIn,
|
|
16
|
+
registration: new EmailPasswordRegistration(repository, utilities, emailPassword),
|
|
17
|
+
utilities,
|
|
18
|
+
});
|
|
19
|
+
return emailSignUp.execute(input);
|
|
20
|
+
};
|
|
21
|
+
this.appsScript = async (input) => {
|
|
22
|
+
if (!appsScript) {
|
|
23
|
+
throw new Error("Apps Script authentication is disabled");
|
|
24
|
+
}
|
|
25
|
+
appsScript.ensureSignUpEnabled();
|
|
26
|
+
const appsScriptSignUp = new SignUp({
|
|
27
|
+
sessionStorage,
|
|
28
|
+
expiresIn,
|
|
29
|
+
registration: new AppsScriptRegistration(utilities, session, repository),
|
|
30
|
+
utilities,
|
|
31
|
+
});
|
|
32
|
+
return appsScriptSignUp.execute(input);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Authentication } from "../authentication/Authentication";
|
|
2
|
+
import { Session } from "../domain/Session";
|
|
3
|
+
import type { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
|
|
4
|
+
export declare class SignIn<T> {
|
|
5
|
+
private readonly sessionStorage;
|
|
6
|
+
private readonly expiresIn;
|
|
7
|
+
private readonly utilities;
|
|
8
|
+
private readonly authentication;
|
|
9
|
+
constructor({ sessionStorage, authentication, utilities, expiresIn, }: {
|
|
10
|
+
sessionStorage: AppsScriptSessionStorage;
|
|
11
|
+
authentication: Authentication<T>;
|
|
12
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
13
|
+
expiresIn: number;
|
|
14
|
+
});
|
|
15
|
+
execute(credential: T): Promise<{
|
|
16
|
+
user: import("..").User;
|
|
17
|
+
session: Session;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Session } from "../domain/Session";
|
|
2
|
+
export class SignIn {
|
|
3
|
+
sessionStorage;
|
|
4
|
+
expiresIn;
|
|
5
|
+
utilities;
|
|
6
|
+
authentication;
|
|
7
|
+
constructor({ sessionStorage, authentication, utilities, expiresIn, }) {
|
|
8
|
+
// Initialize SignIn with db, schema, and session
|
|
9
|
+
this.sessionStorage = sessionStorage;
|
|
10
|
+
this.authentication = authentication;
|
|
11
|
+
this.utilities = utilities;
|
|
12
|
+
this.expiresIn = expiresIn;
|
|
13
|
+
}
|
|
14
|
+
async execute(credential) {
|
|
15
|
+
// Implement the sign-in logic using the provided authentication method
|
|
16
|
+
const user = await this.authentication.verify(credential);
|
|
17
|
+
const session = new Session({
|
|
18
|
+
id: this.utilities.getUuid(),
|
|
19
|
+
userId: user.id,
|
|
20
|
+
createdAt: new Date(),
|
|
21
|
+
expiresAt: new Date(Date.now() + this.expiresIn),
|
|
22
|
+
});
|
|
23
|
+
await this.sessionStorage.save(session);
|
|
24
|
+
return { user, session };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Session } from "../domain/Session";
|
|
2
|
+
import { User } from "../domain/User";
|
|
3
|
+
import type { Registration } from "../registration/Registration";
|
|
4
|
+
import type { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
|
|
5
|
+
export declare class SignUp<T> {
|
|
6
|
+
private readonly sessionStorage;
|
|
7
|
+
private readonly registration;
|
|
8
|
+
private readonly utilities;
|
|
9
|
+
private readonly expiresIn;
|
|
10
|
+
constructor({ sessionStorage, registration, utilities, expiresIn, }: {
|
|
11
|
+
sessionStorage: AppsScriptSessionStorage;
|
|
12
|
+
registration: Registration<T>;
|
|
13
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
14
|
+
expiresIn: number;
|
|
15
|
+
});
|
|
16
|
+
execute(input: T): Promise<{
|
|
17
|
+
user: User;
|
|
18
|
+
session: Session;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Session } from "../domain/Session";
|
|
2
|
+
export class SignUp {
|
|
3
|
+
sessionStorage;
|
|
4
|
+
registration;
|
|
5
|
+
utilities;
|
|
6
|
+
expiresIn;
|
|
7
|
+
constructor({ sessionStorage, registration, utilities, expiresIn, }) {
|
|
8
|
+
this.sessionStorage = sessionStorage;
|
|
9
|
+
this.registration = registration;
|
|
10
|
+
this.utilities = utilities;
|
|
11
|
+
this.expiresIn = expiresIn;
|
|
12
|
+
}
|
|
13
|
+
async execute(input) {
|
|
14
|
+
const user = await this.registration.register(input);
|
|
15
|
+
const now = new Date();
|
|
16
|
+
const session = new Session({
|
|
17
|
+
id: this.utilities.getUuid(),
|
|
18
|
+
userId: user.id,
|
|
19
|
+
createdAt: now,
|
|
20
|
+
expiresAt: new Date(now.getTime() + this.expiresIn),
|
|
21
|
+
});
|
|
22
|
+
await this.sessionStorage.save(session);
|
|
23
|
+
return { user, session };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { User } from "../domain/User";
|
|
2
|
+
import type { AppsScriptAuthRepository } from "../storage/AppsScriptAuthRepository";
|
|
3
|
+
import type { Authentication } from "./Authentication";
|
|
4
|
+
export type AppsScriptAuthenticationOptions = {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
isSignupEnabled?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare class AppsScriptAuthenticationConfig {
|
|
9
|
+
readonly enabled: boolean;
|
|
10
|
+
readonly isSignupEnabled: boolean;
|
|
11
|
+
constructor({ enabled, isSignupEnabled, }: {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
isSignupEnabled?: boolean;
|
|
14
|
+
});
|
|
15
|
+
ensureSignInEnabled(): void;
|
|
16
|
+
ensureSignUpEnabled(): void;
|
|
17
|
+
}
|
|
18
|
+
export type AppsScriptCredential = Record<string, never>;
|
|
19
|
+
export declare class AppsScriptAuthentication implements Authentication<AppsScriptCredential> {
|
|
20
|
+
private readonly repository;
|
|
21
|
+
private readonly session;
|
|
22
|
+
constructor(repository: AppsScriptAuthRepository, session: GoogleAppsScript.Base.Session);
|
|
23
|
+
verify(__credential: AppsScriptCredential): Promise<User>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { authPattern } from "../AuthPattern";
|
|
2
|
+
import { AppsScriptIdentity } from "../identity/AppsScriptIdentity";
|
|
3
|
+
export class AppsScriptAuthenticationConfig {
|
|
4
|
+
enabled;
|
|
5
|
+
isSignupEnabled;
|
|
6
|
+
constructor({ enabled = false, isSignupEnabled = true, }) {
|
|
7
|
+
this.enabled = enabled;
|
|
8
|
+
this.isSignupEnabled = isSignupEnabled;
|
|
9
|
+
}
|
|
10
|
+
ensureSignInEnabled() {
|
|
11
|
+
if (!this.enabled) {
|
|
12
|
+
throw new Error("Apps Script authentication is disabled");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
ensureSignUpEnabled() {
|
|
16
|
+
this.ensureSignInEnabled();
|
|
17
|
+
if (!this.isSignupEnabled) {
|
|
18
|
+
throw new Error("Apps Script signup is disabled");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class AppsScriptAuthentication {
|
|
23
|
+
repository;
|
|
24
|
+
session;
|
|
25
|
+
constructor(repository, session) {
|
|
26
|
+
this.repository = repository;
|
|
27
|
+
this.session = session;
|
|
28
|
+
}
|
|
29
|
+
async verify(__credential) {
|
|
30
|
+
const email = this.session.getActiveUser().getEmail();
|
|
31
|
+
if (!email) {
|
|
32
|
+
throw new Error("No active user found");
|
|
33
|
+
}
|
|
34
|
+
const account = await this.repository.account.findByIdentity(authPattern.appsScript, email);
|
|
35
|
+
if (!account) {
|
|
36
|
+
throw new Error("Account not found");
|
|
37
|
+
}
|
|
38
|
+
if (!(account.identity instanceof AppsScriptIdentity)) {
|
|
39
|
+
throw new Error("Invalid identity");
|
|
40
|
+
}
|
|
41
|
+
const user = await this.repository.user.find(account.userId);
|
|
42
|
+
if (!user) {
|
|
43
|
+
throw new Error("User not found");
|
|
44
|
+
}
|
|
45
|
+
return user;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PasswordResetDelivery } from "../api/AppsScriptAuthPassword";
|
|
2
|
+
import type { User } from "../domain/User";
|
|
3
|
+
import type { AppsScriptAuthRepository } from "../storage/AppsScriptAuthRepository";
|
|
4
|
+
import type { Authentication } from "./Authentication";
|
|
5
|
+
export type EmailPasswordAuthOptions = {
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
isSignupEnabled?: boolean;
|
|
8
|
+
pepper: string;
|
|
9
|
+
iterations?: number;
|
|
10
|
+
passwordReset?: {
|
|
11
|
+
delivery: PasswordResetDelivery;
|
|
12
|
+
expiresIn?: number;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare class EmailPasswordAuthConfig {
|
|
16
|
+
static readonly DEFAULT_ITERATIONS = 300;
|
|
17
|
+
static readonly MAX_ITERATIONS = 1000;
|
|
18
|
+
readonly enabled: boolean;
|
|
19
|
+
readonly isSignupEnabled: boolean;
|
|
20
|
+
readonly pepper: string;
|
|
21
|
+
readonly iterations: number;
|
|
22
|
+
readonly passwordReset?: {
|
|
23
|
+
delivery: PasswordResetDelivery;
|
|
24
|
+
expiresIn?: number;
|
|
25
|
+
};
|
|
26
|
+
constructor({ enabled, isSignupEnabled, pepper, iterations, passwordReset, }: EmailPasswordAuthOptions);
|
|
27
|
+
ensureSignInEnabled(): void;
|
|
28
|
+
ensureSignUpEnabled(): void;
|
|
29
|
+
ensureIterationsWithinLimit(iterations: number): void;
|
|
30
|
+
ensurePasswordResetEnabled(): void;
|
|
31
|
+
}
|
|
32
|
+
export interface EmailPasswordCredential {
|
|
33
|
+
readonly email: string;
|
|
34
|
+
readonly password: string;
|
|
35
|
+
}
|
|
36
|
+
export declare class EmailPasswordAuthentication implements Authentication<EmailPasswordCredential> {
|
|
37
|
+
private readonly repository;
|
|
38
|
+
private readonly utilities;
|
|
39
|
+
private readonly config;
|
|
40
|
+
constructor(repository: AppsScriptAuthRepository, utilities: GoogleAppsScript.Utilities.Utilities, config: EmailPasswordAuthConfig);
|
|
41
|
+
verify(credential: EmailPasswordCredential): Promise<User>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { authPattern } from "../AuthPattern";
|
|
2
|
+
import { Password } from "../domain/Password";
|
|
3
|
+
import { EmailPasswordIdentity } from "../identity/EmailPasswordIdentity";
|
|
4
|
+
export class EmailPasswordAuthConfig {
|
|
5
|
+
static DEFAULT_ITERATIONS = 300;
|
|
6
|
+
static MAX_ITERATIONS = 1000;
|
|
7
|
+
enabled;
|
|
8
|
+
isSignupEnabled;
|
|
9
|
+
pepper;
|
|
10
|
+
iterations;
|
|
11
|
+
passwordReset;
|
|
12
|
+
constructor({ enabled = false, isSignupEnabled = true, pepper, iterations = EmailPasswordAuthConfig.DEFAULT_ITERATIONS, passwordReset, }) {
|
|
13
|
+
if (enabled && !pepper.trim()) {
|
|
14
|
+
throw new Error("Pepper is required when email and password authentication is enabled");
|
|
15
|
+
}
|
|
16
|
+
this.ensureIterationsWithinLimit(iterations);
|
|
17
|
+
this.enabled = enabled;
|
|
18
|
+
this.isSignupEnabled = isSignupEnabled;
|
|
19
|
+
this.pepper = pepper;
|
|
20
|
+
this.iterations = iterations;
|
|
21
|
+
this.passwordReset = passwordReset;
|
|
22
|
+
}
|
|
23
|
+
ensureSignInEnabled() {
|
|
24
|
+
if (!this.enabled) {
|
|
25
|
+
throw new Error("Email and password authentication is disabled");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
ensureSignUpEnabled() {
|
|
29
|
+
this.ensureSignInEnabled();
|
|
30
|
+
if (!this.isSignupEnabled) {
|
|
31
|
+
throw new Error("Email and password signup is disabled");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
ensureIterationsWithinLimit(iterations) {
|
|
35
|
+
if (!Number.isInteger(iterations) || iterations < 1) {
|
|
36
|
+
throw new Error("Password hash iterations must be a positive integer");
|
|
37
|
+
}
|
|
38
|
+
if (iterations > EmailPasswordAuthConfig.MAX_ITERATIONS) {
|
|
39
|
+
throw new Error(`Password hash iterations must not exceed ${EmailPasswordAuthConfig.MAX_ITERATIONS}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
ensurePasswordResetEnabled() {
|
|
43
|
+
if (!this.passwordReset) {
|
|
44
|
+
throw new Error("Password reset is not enabled");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class EmailPasswordAuthentication {
|
|
49
|
+
repository;
|
|
50
|
+
utilities;
|
|
51
|
+
config;
|
|
52
|
+
constructor(repository, utilities, config) {
|
|
53
|
+
this.repository = repository;
|
|
54
|
+
this.utilities = utilities;
|
|
55
|
+
this.config = config;
|
|
56
|
+
}
|
|
57
|
+
async verify(credential) {
|
|
58
|
+
const account = await this.repository.account.findByIdentity(authPattern.emailPassword, credential.email);
|
|
59
|
+
if (!account) {
|
|
60
|
+
throw new Error("Account not found");
|
|
61
|
+
}
|
|
62
|
+
if (!(account.identity instanceof EmailPasswordIdentity)) {
|
|
63
|
+
throw new Error("Invalid identity");
|
|
64
|
+
}
|
|
65
|
+
const password = new Password(credential.password, this.utilities, this.config.pepper, this.config.iterations);
|
|
66
|
+
if (!(await account.identity.verify(password, account.id))) {
|
|
67
|
+
throw new Error("Invalid password");
|
|
68
|
+
}
|
|
69
|
+
const user = await this.repository.user.find(account.userId);
|
|
70
|
+
if (!user) {
|
|
71
|
+
throw new Error("User not found");
|
|
72
|
+
}
|
|
73
|
+
return user;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProviderIdentity } from "../identity/ProviderIdentity";
|
|
2
|
+
export declare class Account {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly userId: string;
|
|
5
|
+
readonly identity: ProviderIdentity;
|
|
6
|
+
constructor({ id, userId, identity, }: {
|
|
7
|
+
id: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
identity: ProviderIdentity;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class Password {
|
|
2
|
+
readonly value: string;
|
|
3
|
+
private readonly utilities;
|
|
4
|
+
private readonly pepper;
|
|
5
|
+
private readonly iterations;
|
|
6
|
+
constructor(value: string, utilities: GoogleAppsScript.Utilities.Utilities, pepper: string, iterations: number);
|
|
7
|
+
hash(salt: string): Promise<HashedPassword>;
|
|
8
|
+
}
|
|
9
|
+
export declare class HashedPassword {
|
|
10
|
+
readonly value: string;
|
|
11
|
+
constructor({ value }: {
|
|
12
|
+
value: string;
|
|
13
|
+
});
|
|
14
|
+
verify(password: Password, salt: string): Promise<boolean>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class Password {
|
|
2
|
+
value;
|
|
3
|
+
utilities;
|
|
4
|
+
pepper;
|
|
5
|
+
iterations;
|
|
6
|
+
constructor(value, utilities, pepper, iterations) {
|
|
7
|
+
this.value = value;
|
|
8
|
+
this.utilities = utilities;
|
|
9
|
+
this.pepper = pepper;
|
|
10
|
+
this.iterations = iterations;
|
|
11
|
+
}
|
|
12
|
+
async hash(salt) {
|
|
13
|
+
const passwordBytes = Array.from(this.utilities.newBlob(this.value + this.pepper).getBytes());
|
|
14
|
+
const saltBytes = [
|
|
15
|
+
...Array.from(this.utilities.newBlob(salt).getBytes()),
|
|
16
|
+
0,
|
|
17
|
+
0,
|
|
18
|
+
0,
|
|
19
|
+
1,
|
|
20
|
+
];
|
|
21
|
+
let previous = this.utilities.computeHmacSha256Signature(saltBytes, passwordBytes);
|
|
22
|
+
const result = [...previous];
|
|
23
|
+
for (let i = 1; i < this.iterations; i++) {
|
|
24
|
+
previous = this.utilities.computeHmacSha256Signature(previous, passwordBytes);
|
|
25
|
+
for (let j = 0; j < result.length; j++) {
|
|
26
|
+
result[j] = result[j] ^ previous[j];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const hash = result
|
|
30
|
+
.map((byte) => (byte < 0 ? byte + 256 : byte))
|
|
31
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
32
|
+
.join("");
|
|
33
|
+
return new HashedPassword({
|
|
34
|
+
value: hash,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class HashedPassword {
|
|
39
|
+
value;
|
|
40
|
+
constructor({ value }) {
|
|
41
|
+
this.value = value;
|
|
42
|
+
}
|
|
43
|
+
async verify(password, salt) {
|
|
44
|
+
const hashed = await password.hash(salt);
|
|
45
|
+
if (this.value.length !== hashed.value.length) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
let difference = 0;
|
|
49
|
+
for (let i = 0; i < this.value.length; i++) {
|
|
50
|
+
difference |= this.value.charCodeAt(i) ^ hashed.value.charCodeAt(i);
|
|
51
|
+
}
|
|
52
|
+
return difference === 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Account } from "./Account";
|
|
2
|
+
import type { HashedPassword } from "./Password";
|
|
3
|
+
import { PasswordReset } from "./PasswordReset";
|
|
4
|
+
export declare class PasswordCredential {
|
|
5
|
+
readonly account: Account;
|
|
6
|
+
readonly reset: PasswordReset | null;
|
|
7
|
+
constructor({ account, reset, }: {
|
|
8
|
+
account: Account;
|
|
9
|
+
reset: PasswordReset | null;
|
|
10
|
+
});
|
|
11
|
+
static generate({ account, utilities, expiresAt, }: {
|
|
12
|
+
account: Account;
|
|
13
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
14
|
+
expiresAt: Date;
|
|
15
|
+
}): {
|
|
16
|
+
credential: PasswordCredential;
|
|
17
|
+
token: string;
|
|
18
|
+
};
|
|
19
|
+
resetPassword({ token, newPassword, utilities, now, }: {
|
|
20
|
+
token: string;
|
|
21
|
+
newPassword: HashedPassword;
|
|
22
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
23
|
+
now: Date;
|
|
24
|
+
}): PasswordCredential;
|
|
25
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { EmailPasswordIdentity } from "../identity/EmailPasswordIdentity";
|
|
2
|
+
import { Account } from "./Account";
|
|
3
|
+
import { InvalidPasswordResetError } from "./Errors";
|
|
4
|
+
import { PasswordReset } from "./PasswordReset";
|
|
5
|
+
export class PasswordCredential {
|
|
6
|
+
account;
|
|
7
|
+
reset;
|
|
8
|
+
constructor({ account, reset, }) {
|
|
9
|
+
this.account = account;
|
|
10
|
+
this.reset = reset;
|
|
11
|
+
}
|
|
12
|
+
static generate({ account, utilities, expiresAt, }) {
|
|
13
|
+
if (!(account.identity instanceof EmailPasswordIdentity)) {
|
|
14
|
+
throw new Error("Invalid account identity");
|
|
15
|
+
}
|
|
16
|
+
const { reset, token } = PasswordReset.create({
|
|
17
|
+
accountId: account.id,
|
|
18
|
+
utilities,
|
|
19
|
+
expiresAt,
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
credential: new PasswordCredential({
|
|
23
|
+
account,
|
|
24
|
+
reset,
|
|
25
|
+
}),
|
|
26
|
+
token,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
resetPassword({ token, newPassword, utilities, now, }) {
|
|
30
|
+
if (this.reset === null) {
|
|
31
|
+
throw new InvalidPasswordResetError();
|
|
32
|
+
}
|
|
33
|
+
if (this.reset.accountId !== this.account.id) {
|
|
34
|
+
throw new InvalidPasswordResetError();
|
|
35
|
+
}
|
|
36
|
+
if (this.reset.enabled === false) {
|
|
37
|
+
throw new InvalidPasswordResetError();
|
|
38
|
+
}
|
|
39
|
+
if (this.reset.isExpired(now)) {
|
|
40
|
+
throw new InvalidPasswordResetError();
|
|
41
|
+
}
|
|
42
|
+
if (this.reset.verify(token, utilities) === false) {
|
|
43
|
+
throw new InvalidPasswordResetError();
|
|
44
|
+
}
|
|
45
|
+
if (!(this.account.identity instanceof EmailPasswordIdentity)) {
|
|
46
|
+
throw new Error("Invalid account identity");
|
|
47
|
+
}
|
|
48
|
+
return new PasswordCredential({
|
|
49
|
+
account: new Account({
|
|
50
|
+
id: this.account.id,
|
|
51
|
+
userId: this.account.userId,
|
|
52
|
+
identity: this.account.identity.changePassword(newPassword),
|
|
53
|
+
}),
|
|
54
|
+
reset: this.reset.disable(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|