| 112 | } |
| 113 | |
| 114 | export class SingleUserOAuthProvider implements OAuthServerProvider { |
| 115 | readonly clientsStore: OAuthRegisteredClientsStore; |
| 116 | private readonly codes = new Map<string, AuthorizationCodeRecord>(); |
| 117 | private readonly oauthStore: SqliteOAuthStore; |
| 118 | private readonly resourceServerUrl: URL; |
| 119 | |
| 120 | constructor( |
| 121 | private readonly config: OAuthConfig, |
| 122 | resourceServerUrl: URL, |
| 123 | stateDir: string, |
| 124 | ) { |
| 125 | this.resourceServerUrl = resourceUrlFromServerUrl(resourceServerUrl); |
| 126 | this.oauthStore = new SqliteOAuthStore(stateDir); |
| 127 | this.clientsStore = new SqliteOAuthClientsStore(this.oauthStore, config.allowedRedirectHosts); |
| 128 | } |
| 129 | |
| 130 | async authorize( |
| 131 | client: OAuthClientInformationFull, |
| 132 | params: AuthorizationParams, |
| 133 | res: Response, |
| 134 | ): Promise<void> { |
| 135 | if (!params.resource || !checkResourceAllowed({ requestedResource: params.resource, configuredResource: this.resourceServerUrl })) { |
| 136 | throw new InvalidRequestError("Invalid or missing OAuth resource"); |
| 137 | } |
| 138 | if (!requestedScopesAllowed(params.scopes ?? [], this.config.scopes)) { |
| 139 | throw new InvalidRequestError("Requested scope is not supported"); |
| 140 | } |
| 141 | |
| 142 | if (res.req.method !== "POST") { |
| 143 | res.status(200).setHeader("Content-Type", "text/html; charset=utf-8"); |
| 144 | res.send( |
| 145 | formHtml({ |
| 146 | clientName: client.client_name ?? client.client_id, |
| 147 | scopes: params.scopes ?? this.config.scopes, |
| 148 | resource: params.resource, |
| 149 | fields: authorizationFormFields(client, params), |
| 150 | }), |
| 151 | ); |
| 152 | return; |
| 153 | } |
| 154 | |
| 155 | const providedToken = String(res.req.body?.owner_token ?? ""); |
| 156 | if (!safeEquals(providedToken, this.config.ownerToken)) { |
| 157 | res.status(401).setHeader("Content-Type", "text/html; charset=utf-8"); |
| 158 | res.send( |
| 159 | formHtml({ |
| 160 | error: "The Owner password was not accepted.", |
| 161 | clientName: client.client_name ?? client.client_id, |
| 162 | scopes: params.scopes ?? this.config.scopes, |
| 163 | resource: params.resource, |
| 164 | fields: authorizationFormFields(client, params), |
| 165 | }), |
| 166 | ); |
| 167 | return; |
| 168 | } |
| 169 | |
| 170 | const code = `code-${randomUUID()}`; |
| 171 | this.codes.set(code, { |
nothing calls this directly
no outgoing calls
no test coverage detected