tmp
This commit is contained in:
parent
8ea1288234
commit
049dbfeb66
2 changed files with 89 additions and 5 deletions
|
@ -26,6 +26,11 @@ import { MetaService } from '@/core/MetaService.js';
|
||||||
import fastifyFormbody from '@fastify/formbody';
|
import fastifyFormbody from '@fastify/formbody';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import fastifyExpress from '@fastify/express';
|
import fastifyExpress from '@fastify/express';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
import type { AccessTokensRepository, UsersRepository } from '@/models/index.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { UserCacheService } from '@/core/UserCacheService.js';
|
||||||
|
import type { LocalUser } from '@/models/entities/User.js';
|
||||||
|
|
||||||
// https://indieauth.spec.indieweb.org/#client-identifier
|
// https://indieauth.spec.indieweb.org/#client-identifier
|
||||||
function validateClientId(raw: string): URL {
|
function validateClientId(raw: string): URL {
|
||||||
|
@ -263,6 +268,12 @@ async function fetchFromClientId(httpRequestService: HttpRequestService, id: str
|
||||||
// };
|
// };
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
function pkceS256(codeVerifier: string) {
|
||||||
|
return crypto.createHash('sha256')
|
||||||
|
.update(codeVerifier, 'ascii')
|
||||||
|
.digest('base64url');
|
||||||
|
}
|
||||||
|
|
||||||
type OmitFirstElement<T extends unknown[]> = T extends [unknown, ...(infer R)]
|
type OmitFirstElement<T extends unknown[]> = T extends [unknown, ...(infer R)]
|
||||||
? R
|
? R
|
||||||
: [];
|
: [];
|
||||||
|
@ -290,6 +301,12 @@ export class OAuth2ProviderService {
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
@Inject(DI.accessTokensRepository)
|
||||||
|
accessTokensRepository: AccessTokensRepository,
|
||||||
|
idService: IdService,
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
private userCacheService: UserCacheService,
|
||||||
) {
|
) {
|
||||||
// this.#provider = new Provider(config.url, {
|
// this.#provider = new Provider(config.url, {
|
||||||
// clientAuthMethods: ['none'],
|
// clientAuthMethods: ['none'],
|
||||||
|
@ -314,11 +331,69 @@ export class OAuth2ProviderService {
|
||||||
// console.log(error);
|
// console.log(error);
|
||||||
// },
|
// },
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
const TEMP_GRANT_CODES: Record<string, {
|
||||||
|
clientId: string,
|
||||||
|
userId: string,
|
||||||
|
redirectUri: string,
|
||||||
|
codeChallenge: string,
|
||||||
|
scopes: string[],
|
||||||
|
}> = {};
|
||||||
|
|
||||||
this.#server.grant(oauth2Pkce.extensions());
|
this.#server.grant(oauth2Pkce.extensions());
|
||||||
this.#server.grant(oauth2orize.grant.code((client, redirectUri, user, ares, done) => {
|
this.#server.grant(oauth2orize.grant.code((client, redirectUri, token, ares, areq, done) => {
|
||||||
console.log('HIT grant code:', client, redirectUri, user, ares);
|
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
||||||
const code = secureRndstr(32, true);
|
console.log('HIT grant code:', client, redirectUri, token, ares, areq);
|
||||||
done(null, code);
|
const code = secureRndstr(32, true);
|
||||||
|
|
||||||
|
const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token,
|
||||||
|
() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('No such user');
|
||||||
|
}
|
||||||
|
|
||||||
|
TEMP_GRANT_CODES[code] = {
|
||||||
|
clientId: client,
|
||||||
|
userId: user.id,
|
||||||
|
redirectUri,
|
||||||
|
codeChallenge: areq.codeChallenge,
|
||||||
|
scopes: areq.scope,
|
||||||
|
};
|
||||||
|
return [code];
|
||||||
|
})().then(args => done(null, ...args), err => done(err));
|
||||||
|
}));
|
||||||
|
this.#server.exchange(oauth2orize.exchange.code((client, code, redirectUri, body, done) => {
|
||||||
|
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
||||||
|
const granted = TEMP_GRANT_CODES[code];
|
||||||
|
console.log(granted, body, code, redirectUri);
|
||||||
|
if (!granted) {
|
||||||
|
return [false];
|
||||||
|
}
|
||||||
|
delete TEMP_GRANT_CODES[code];
|
||||||
|
if (!granted.scopes.length) return [false];
|
||||||
|
if (body.client_id !== granted.clientId) return [false];
|
||||||
|
if (redirectUri !== granted.redirectUri) return [false];
|
||||||
|
if (!body.code_verifier || pkceS256(body.code_verifier) !== granted.codeChallenge) return [false];
|
||||||
|
|
||||||
|
const accessToken = secureRndstr(128, true);
|
||||||
|
const refreshToken = secureRndstr(128, true);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// Insert access token doc
|
||||||
|
await accessTokensRepository.insert({
|
||||||
|
id: idService.genId(),
|
||||||
|
createdAt: now,
|
||||||
|
lastUsedAt: now,
|
||||||
|
userId: granted.userId,
|
||||||
|
token: accessToken,
|
||||||
|
hash: accessToken,
|
||||||
|
name: granted.clientId,
|
||||||
|
permission: granted.scopes,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [accessToken, refreshToken];
|
||||||
|
})().then(args => done(null, ...args), err => done(err));
|
||||||
}));
|
}));
|
||||||
this.#server.serializeClient((client, done) => done(null, client));
|
this.#server.serializeClient((client, done) => done(null, client));
|
||||||
this.#server.deserializeClient((id, done) => done(null, id));
|
this.#server.deserializeClient((id, done) => done(null, id));
|
||||||
|
@ -373,6 +448,7 @@ export class OAuth2ProviderService {
|
||||||
throw new Error('`code_challenge_method` parameter must be set as S256');
|
throw new Error('`code_challenge_method` parameter must be set as S256');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'no-store');
|
||||||
return await reply.view('oauth', {
|
return await reply.view('oauth', {
|
||||||
transactionId: oauth2?.transactionID,
|
transactionId: oauth2?.transactionID,
|
||||||
});
|
});
|
||||||
|
@ -414,7 +490,14 @@ export class OAuth2ProviderService {
|
||||||
// for (const middleware of this.#server.decision()) {
|
// for (const middleware of this.#server.decision()) {
|
||||||
|
|
||||||
fastify.use('/oauth/decision', bodyParser.urlencoded({ extended: false }));
|
fastify.use('/oauth/decision', bodyParser.urlencoded({ extended: false }));
|
||||||
fastify.use('/oauth/decision', this.#server.decision());
|
fastify.use('/oauth/decision', this.#server.decision((req, done) => {
|
||||||
|
console.log('HIT decision:', req.oauth2, (req as any).body);
|
||||||
|
req.user = (req as any).body.login_token;
|
||||||
|
done(null, undefined);
|
||||||
|
}));
|
||||||
|
|
||||||
|
fastify.use('/oauth/token', bodyParser.json({ strict: true }));
|
||||||
|
fastify.use('/oauth/token', this.#server.token());
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fastify.use('/oauth', this.#provider.callback());
|
// fastify.use('/oauth', this.#provider.callback());
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<div v-if="name">{{ $t('_auth.shareAccess', { name }) }}</div>
|
<div v-if="name">{{ $t('_auth.shareAccess', { name }) }}</div>
|
||||||
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
||||||
<form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post">
|
<form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post">
|
||||||
|
<input name="login_token" type="hidden" :value="$i.token"/>
|
||||||
<input name="transaction_id" type="hidden" :value="transactionIdMeta?.content"/>
|
<input name="transaction_id" type="hidden" :value="transactionIdMeta?.content"/>
|
||||||
<MkButton inline name="cancel">{{ i18n.ts.cancel }}</MkButton>
|
<MkButton inline name="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||||
<MkButton inline primary>{{ i18n.ts.accept }}</MkButton>
|
<MkButton inline primary>{{ i18n.ts.accept }}</MkButton>
|
||||||
|
|
Loading…
Reference in a new issue