Skip to content

Commit

Permalink
Make optional properties nullable and improve example services
Browse files Browse the repository at this point in the history
  • Loading branch information
KyleJune committed Oct 9, 2021
1 parent 6e74131 commit 08ed327
Show file tree
Hide file tree
Showing 19 changed files with 342 additions and 192 deletions.
4 changes: 2 additions & 2 deletions examples/oak-localstorage/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { clientService, userService } from "./services/mod.ts";

localStorage.clear();

userService.put({ username: "kyle", password: "qwerty" });
userService.put({ username: "john", password: "doe" });
userService.create({ username: "kyle", password: "qwerty" });
userService.create({ username: "john", password: "doe" });

clientService.put({
id: "1000",
Expand Down
8 changes: 4 additions & 4 deletions examples/oak-localstorage/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ router
if (expiresIn) {
session.accessTokenExpiresAt = new Date(now + (expiresIn * 1000));
}
session.user = undefined;
session.state = undefined;
session.codeVerifier = undefined;
session.redirectUri = undefined;
session.user = null;
session.state = null;
session.codeVerifier = null;
session.redirectUri = null;
await sessionService.patch(session);
response.redirect(redirectUri);
} else {
Expand Down
4 changes: 2 additions & 2 deletions examples/oak-localstorage/models/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { User } from "./user.ts";

export interface Client extends ClientInterface {
/** Secret used for authenticating a client. */
secret?: string;
secret?: string | null;
/** A user that is controlled by the client. */
user?: User;
user?: User | null;
}
14 changes: 7 additions & 7 deletions examples/oak-localstorage/models/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { User } from "./user.ts";
export interface Session {
id: string;
csrf: string;
user?: User;
state?: string;
redirectUri?: string;
codeVerifier?: string;
accessToken?: string;
refreshToken?: string;
accessTokenExpiresAt?: Date;
user?: User | null;
state?: string | null;
redirectUri?: string | null;
codeVerifier?: string | null;
accessToken?: string | null;
refreshToken?: string | null;
accessTokenExpiresAt?: Date | null;
}
5 changes: 3 additions & 2 deletions examples/oak-localstorage/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface User {
id: string;
username: string;
password?: string;
email?: string;
password?: string | null;
email?: string | null;
}
116 changes: 80 additions & 36 deletions examples/oak-localstorage/services/authorization_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface AuthorizationCodeInternal {
code: string;
expiresAt: string;
clientId: string;
username: string;
userId: string;
scope?: string;
redirectUri?: string;
challenge?: string;
Expand Down Expand Up @@ -47,14 +47,52 @@ export class AuthorizationCodeService
code,
expiresAt: expiresAt.toJSON(),
clientId: client.id,
username: user.username,
scope: scope?.toJSON(),
userId: user.id,
};
if (scope) next.scope = scope.toJSON();
if (redirectUri) next.redirectUri = redirectUri;
if (challenge) next.challenge = challenge;
if (challengeMethod) next.challengeMethod = challengeMethod;
localStorage.setItem(`authorizationCode:${code}`, JSON.stringify(next));
return Promise.resolve();
}

async patch(
authorizationCode:
& Partial<AuthorizationCode<Client, User, Scope>>
& Pick<AuthorizationCode<Client, User, Scope>, "code">,
): Promise<void> {
const {
code,
expiresAt,
client,
user,
scope,
redirectUri,
challenge,
challengeMethod,
};
} = authorizationCode;
const current = await this.getInternal(code);
if (!current) throw new Error("authorization code not found");
const next: AuthorizationCodeInternal = { ...current, code };

if (expiresAt) next.expiresAt = expiresAt.toJSON();
if (client) next.clientId = client.id;
if (user) next.userId = user.id;

if (scope) next.scope = scope.toJSON();
else if (scope === null) delete next.scope;

if (redirectUri) next.redirectUri = redirectUri;
else if (redirectUri === null) delete next.redirectUri;

if (challenge) next.challenge = challenge;
else if (challenge === null) delete next.challenge;

if (challengeMethod) next.challengeMethod = challengeMethod;
else if (challengeMethod === null) delete next.challengeMethod;

localStorage.setItem(`authorizationCode:${code}`, JSON.stringify(next));
return Promise.resolve();
}

delete(
Expand All @@ -69,44 +107,50 @@ export class AuthorizationCodeService
return Promise.resolve(existed);
}

async get(
private getInternal(
code: string,
): Promise<AuthorizationCode<Client, User, Scope> | undefined> {
): Promise<AuthorizationCodeInternal | undefined> {
const internalText = localStorage.getItem(`authorizationCode:${code}`);
const internal: AuthorizationCodeInternal | undefined = internalText
? JSON.parse(internalText)
: undefined;
let authorizationCode: AuthorizationCode<Client, User, Scope> | undefined =
undefined;
if (internal) {
const {
return Promise.resolve(internalText ? JSON.parse(internalText) : undefined);
}

private async toExternal(
internal: AuthorizationCodeInternal,
): Promise<AuthorizationCode<Client, User, Scope> | undefined> {
const {
code,
expiresAt,
clientId,
userId,
scope,
redirectUri,
challenge,
challengeMethod,
} = internal;
const client = await this.clientService.get(clientId);
const user = client && await this.userService.get(userId);
if (client && user) {
const authorizationCode: AuthorizationCode<Client, User, Scope> = {
code,
expiresAt,
clientId,
username,
scope,
expiresAt: new Date(expiresAt),
client,
user,
redirectUri,
challenge,
challengeMethod,
} = internal;
const client = await this.clientService.get(clientId);
const user = client && await this.userService.get(username);
if (client && user) {
authorizationCode = {
code,
expiresAt: new Date(expiresAt),
client,
user,
redirectUri,
challenge,
challengeMethod,
};
if (scope) authorizationCode.scope = Scope.from(scope);
} else {
await this.delete(code);
}
};
if (scope) authorizationCode.scope = Scope.from(scope);
return authorizationCode;
} else {
await this.delete(code);
}
return authorizationCode;
}

async get(
code: string,
): Promise<AuthorizationCode<Client, User, Scope> | undefined> {
const internal = await this.getInternal(code);
return internal ? await this.toExternal(internal) : undefined;
}

async save(
Expand Down
56 changes: 33 additions & 23 deletions examples/oak-localstorage/services/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface ClientInternal {
redirectUris?: string[];
accessTokenLifetime?: number;
refreshTokenLifetime?: number;
username?: string;
userId?: string;
}

export class ClientService extends AbstractClientService<Client, User> {
Expand All @@ -31,16 +31,13 @@ export class ClientService extends AbstractClientService<Client, User> {
refreshTokenLifetime,
user,
} = client;
const { username } = user ?? {};
const next: ClientInternal = {
id,
secret,
grants,
redirectUris,
accessTokenLifetime,
refreshTokenLifetime,
username,
};
const next: ClientInternal = { id };
if (secret) next.secret = secret;
if (grants) next.grants = grants;
if (redirectUris) next.redirectUris = redirectUris;
if (accessTokenLifetime) next.accessTokenLifetime = accessTokenLifetime;
if (refreshTokenLifetime) next.refreshTokenLifetime = refreshTokenLifetime;
if (user) next.userId = user.id;
localStorage.setItem(`client:${id}`, JSON.stringify(next));
return Promise.resolve();
}
Expand All @@ -55,22 +52,35 @@ export class ClientService extends AbstractClientService<Client, User> {
refreshTokenLifetime,
user,
} = client;
const { username } = user ?? {};
const current = await this.getInternal(id);
if (!current) throw new Error("client not found");
const next: ClientInternal = { ...current };
if ("grants" in client) next.grants = grants;
if ("secret" in client) next.secret = secret;
if ("redirectUris" in client) next.redirectUris = redirectUris;
if ("accessTokenLifetime" in client) {
const next: ClientInternal = { ...current, id };

if (grants) next.grants = grants;
else if (grants === null) delete next.grants;

if (secret) next.secret = secret;
else if (secret === null) delete next.secret;

if (redirectUris) next.redirectUris = redirectUris;
else if (redirectUris === null) delete next.redirectUris;

if (accessTokenLifetime) {
next.accessTokenLifetime = accessTokenLifetime;
} else if (accessTokenLifetime === null) {
delete next.accessTokenLifetime;
}
if ("refreshTokenLifetime" in client) {

if (refreshTokenLifetime) {
next.refreshTokenLifetime = refreshTokenLifetime;
} else if (refreshTokenLifetime === null) {
delete next.refreshTokenLifetime;
}
if ("user" in client) next.username = username;

if (user) next.userId = user.id;
else if (user === null) delete next.userId;

localStorage.setItem(`client:${id}`, JSON.stringify(next));
return Promise.resolve();
}

delete(client: Client | string): Promise<boolean> {
Expand Down Expand Up @@ -107,7 +117,7 @@ export class ClientService extends AbstractClientService<Client, User> {

async get(clientId: string): Promise<Client | undefined> {
const internal = await this.getInternal(clientId);
return internal ? this.toExternal(internal) : undefined;
return internal ? await this.toExternal(internal) : undefined;
}

async getAuthenticated(
Expand All @@ -125,8 +135,8 @@ export class ClientService extends AbstractClientService<Client, User> {
async getUser(client: Client | string): Promise<User | undefined> {
const clientId = typeof client === "string" ? client : client.id;
const internal = await this.getInternal(clientId);
return internal?.username
? await this.userService.get(internal.username)
return internal?.userId
? await this.userService.get(internal.userId)
: undefined;
}
}
Loading

0 comments on commit 08ed327

Please sign in to comment.