Merge branch 'main' of gitlab.com:mstarongitlab/linstrom
This commit is contained in:
commit
53e6418e89
64 changed files with 39325 additions and 36002 deletions
43
frontend-reactive/app/components/auth.ts
Normal file
43
frontend-reactive/app/components/auth.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { service } from '@ember/service';
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import '@simplewebauthn/browser';
|
||||||
|
import {
|
||||||
|
startAuthentication,
|
||||||
|
startRegistration,
|
||||||
|
} from '@simplewebauthn/browser';
|
||||||
|
import type AuthService from 'frontend-reactive/services/auth';
|
||||||
|
|
||||||
|
export interface PasskeySignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Auth extends Component<PasskeySignature> {
|
||||||
|
@tracked username: string = '';
|
||||||
|
@tracked error: string | undefined;
|
||||||
|
@service declare auth: AuthService;
|
||||||
|
|
||||||
|
@action async startLogin() {
|
||||||
|
try {
|
||||||
|
this.auth.startLogin(this.username);
|
||||||
|
} catch (error: any) {
|
||||||
|
this.error = 'Error: ' + error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action async startRegistration() {
|
||||||
|
try {
|
||||||
|
this.auth.startRegistration(this.username);
|
||||||
|
} catch (error: any) {
|
||||||
|
this.error = 'Error: ' + error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
frontend-reactive/app/components/auth/registration-form.hbs
Normal file
69
frontend-reactive/app/components/auth/registration-form.hbs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<div class="registration-form">
|
||||||
|
<h1 class="registration-form-username">username: {{this.args.username}}</h1>
|
||||||
|
<div class="registration-form-displayname">
|
||||||
|
<label>
|
||||||
|
Displayname
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
@value={{this.displayname}}
|
||||||
|
placeholder="Displayname"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="registration-form-description">
|
||||||
|
<label>
|
||||||
|
Description
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
@value={{this.description}}
|
||||||
|
placeholder="Account description"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="registration-form-mail">
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<Input @type="text" @value={{this.email}} placeholder="Email address" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="registration-form-gender">
|
||||||
|
<Util::StringArray
|
||||||
|
@list={{this.gender}}
|
||||||
|
@onNewElement={{this.genderAddedHandler}}
|
||||||
|
@onDeleteElement={{this.genderRemovedHandler}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p>{{this.extracted}}</p>
|
||||||
|
<div class="register-form-being">
|
||||||
|
<Util::Multiselect @elements={{this.beingTypes}} />
|
||||||
|
</div>
|
||||||
|
<div class="register-form-default-post-mode">
|
||||||
|
<Util::OneOfArray
|
||||||
|
@elements={{array "Public" "Local" "Followers" "Direct"}}
|
||||||
|
@selected={{this.defaultpostmode}}
|
||||||
|
@name="default-post-mode"
|
||||||
|
@required={{true}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="register-form-follow-approval">
|
||||||
|
<label>
|
||||||
|
Require approval for follow requests
|
||||||
|
<Input
|
||||||
|
@type="checkbox"
|
||||||
|
name="Follow approval"
|
||||||
|
@checked={{this.args.followapproval}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="register-form-indexable">
|
||||||
|
<label>
|
||||||
|
Whether the account is indexable
|
||||||
|
<Input @type="checkbox" name="Indexable" @checked={{this.indexable}} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="register-form-custom-fields">
|
||||||
|
<Util::MapEdit @list={{this.customProperties}} />
|
||||||
|
</div>
|
||||||
|
{{! TODO: Custom fields (Two string arrays, lets go. Definitely won't be a pain to sync them up) }}
|
||||||
|
{{! TODO: Icon, Background, Banner }}
|
||||||
|
</div>
|
69
frontend-reactive/app/components/auth/registration-form.ts
Normal file
69
frontend-reactive/app/components/auth/registration-form.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
|
export interface AuthRegistrationFormSignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AuthRegistrationForm extends Component<AuthRegistrationFormSignature> {
|
||||||
|
@tracked displayname: string = this.args.username;
|
||||||
|
@tracked description: string = '';
|
||||||
|
@tracked gender: Array<{ value: string }> = [];
|
||||||
|
@tracked beingTypes: Array<{
|
||||||
|
name: string;
|
||||||
|
checked: boolean;
|
||||||
|
description: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
name: 'Human',
|
||||||
|
description: 'Human',
|
||||||
|
checked: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cat',
|
||||||
|
description: 'Cat',
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fox',
|
||||||
|
description: 'Fox',
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dog',
|
||||||
|
description: 'Dog',
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Robot',
|
||||||
|
description: 'Robot',
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Doll',
|
||||||
|
description: 'Doll',
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
@tracked defaultpostmode: string = 'public';
|
||||||
|
@tracked followapproval: boolean = false;
|
||||||
|
// Actual custom properties stored in here
|
||||||
|
@tracked customProperties: Array<{ key: string; value: string }> = [];
|
||||||
|
@tracked indexable: boolean = true;
|
||||||
|
|
||||||
|
genderAddedHandler(newIndex: number) {
|
||||||
|
console.log('gender added');
|
||||||
|
}
|
||||||
|
genderRemovedHandler(removedIndex: number) {
|
||||||
|
console.log('gender removed');
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
<Note::UserHeader
|
<Note::UserHeader
|
||||||
@displayname="{{@note.displayname}}"
|
@displayname="{{@note.displayname}}"
|
||||||
@handle="@{{@note.username}}@{{@note.server}}"
|
@handle="@{{@note.username}}@{{@note.server}}"
|
||||||
|
@server="{{@note.servertype}}"
|
||||||
/>
|
/>
|
||||||
<Note::Content @content="{{@note.content}}" />
|
<Note::Content @content="{{@note.content}}" />
|
||||||
<div class="note-timestamps-container">
|
<div class="note-timestamps-container">
|
||||||
|
|
15
frontend-reactive/app/components/note/formatter.hbs
Normal file
15
frontend-reactive/app/components/note/formatter.hbs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="{{ @classes }}">
|
||||||
|
{{#if (equals @server "mastodon")}}
|
||||||
|
<Note::Formatter::Mastodon>{{ @content }}Masto</Note::Formatter::Mastodon>
|
||||||
|
{{else if (equals @server "misskey")}}
|
||||||
|
<Note::Formatter::Misskey>{{ @content }}Misskey</Note::Formatter::Misskey>
|
||||||
|
{{else if (equals @server "akoma")}}
|
||||||
|
<Note::Formatter::Akoma>{{ @content }}Akoma</Note::Formatter::Akoma>
|
||||||
|
{{else if (equals @server "linstrom")}}
|
||||||
|
<Note::Formatter::Linstrom>{{ @content }}Linstrom</Note::Formatter::Linstrom>
|
||||||
|
{{else if (equals @server "wafrn")}}
|
||||||
|
<Note::Formatter::Wafrn>{{ @content }}Wafrn</Note::Formatter::Wafrn>
|
||||||
|
{{else}}
|
||||||
|
<Note::Formatter::Linstrom>{{ @content }}Unkown:{{@server}}</Note::Formatter::Linstrom>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
|
@ -0,0 +1 @@
|
||||||
|
{{yield}}
|
14
frontend-reactive/app/components/note/formatter/akoma.ts
Normal file
14
frontend-reactive/app/components/note/formatter/akoma.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
|
||||||
|
export interface NoteFormatterAkomaSignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NoteFormatterAkoma extends Component<NoteFormatterAkomaSignature> {}
|
|
@ -0,0 +1 @@
|
||||||
|
{{yield}}
|
14
frontend-reactive/app/components/note/formatter/linstrom.ts
Normal file
14
frontend-reactive/app/components/note/formatter/linstrom.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
|
||||||
|
export interface NoteFormatterLinstromSignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NoteFormatterLinstrom extends Component<NoteFormatterLinstromSignature> {}
|
|
@ -0,0 +1 @@
|
||||||
|
{{yield}}
|
14
frontend-reactive/app/components/note/formatter/mastodon.ts
Normal file
14
frontend-reactive/app/components/note/formatter/mastodon.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
|
||||||
|
export interface NoteFormatterMastodonSignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NoteFormatterMastodon extends Component<NoteFormatterMastodonSignature> {}
|
|
@ -0,0 +1 @@
|
||||||
|
{{yield}}
|
14
frontend-reactive/app/components/note/formatter/misskey.ts
Normal file
14
frontend-reactive/app/components/note/formatter/misskey.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
|
||||||
|
export interface NoteFormatterMisskeySignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NoteFormatterMisskey extends Component<NoteFormatterMisskeySignature> {}
|
|
@ -0,0 +1 @@
|
||||||
|
{{yield}}
|
14
frontend-reactive/app/components/note/formatter/wafrn.ts
Normal file
14
frontend-reactive/app/components/note/formatter/wafrn.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
|
||||||
|
export interface NoteFormatterWafrnSignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NoteFormatterWafrn extends Component<NoteFormatterWafrnSignature> {}
|
|
@ -3,7 +3,8 @@
|
||||||
<p>Pfp</p>
|
<p>Pfp</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-user-name-and-handle">
|
<div class="note-user-name-and-handle">
|
||||||
<p class="note-user-displayname">{{@displayname}}</p>
|
<Note::Formatter @classes="note-user-displayname" @content={{@displayname}} @server={{@server}}/>
|
||||||
|
<p class="note-user-displayname"></p>
|
||||||
<p class="note-user-handle">{{@handle}}</p>
|
<p class="note-user-handle">{{@handle}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,119 +0,0 @@
|
||||||
import { action } from '@ember/object';
|
|
||||||
import Component from '@glimmer/component';
|
|
||||||
import { tracked } from '@glimmer/tracking';
|
|
||||||
import '@simplewebauthn/browser';
|
|
||||||
import {
|
|
||||||
startAuthentication,
|
|
||||||
startRegistration,
|
|
||||||
} from '@simplewebauthn/browser';
|
|
||||||
|
|
||||||
export interface PasskeySignature {
|
|
||||||
// The arguments accepted by the component
|
|
||||||
Args: {};
|
|
||||||
// Any blocks yielded by the component
|
|
||||||
Blocks: {
|
|
||||||
default: [];
|
|
||||||
};
|
|
||||||
// The element to which `...attributes` is applied in the component template
|
|
||||||
Element: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Passkey extends Component<PasskeySignature> {
|
|
||||||
@tracked username: string = '';
|
|
||||||
@tracked error: string | undefined;
|
|
||||||
|
|
||||||
@action async startLogin() {
|
|
||||||
try {
|
|
||||||
// Get login options from your server. Here, we also receive the challenge.
|
|
||||||
const response = await fetch('/webauthn/passkey/loginBegin', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ username: this.username }),
|
|
||||||
});
|
|
||||||
// Check if the login options are ok.
|
|
||||||
if (!response.ok) {
|
|
||||||
const msg = await response.json();
|
|
||||||
throw new Error('Failed to get login options from server: ' + msg);
|
|
||||||
}
|
|
||||||
// Convert the login options to JSON.
|
|
||||||
const options = await response.json();
|
|
||||||
|
|
||||||
// This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
|
|
||||||
// A new assertionResponse is created. This also means that the challenge has been signed.
|
|
||||||
const assertionResponse = await startAuthentication(options.publicKey);
|
|
||||||
|
|
||||||
// Send assertionResponse back to server for verification.
|
|
||||||
const verificationResponse = await fetch(
|
|
||||||
'/webauthn/passkey/loginFinish',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(assertionResponse),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const msg = await verificationResponse.json();
|
|
||||||
if (verificationResponse.ok) {
|
|
||||||
this.error = undefined;
|
|
||||||
} else {
|
|
||||||
this.error = msg;
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
this.error = 'Error: ' + error.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action async startRegistration() {
|
|
||||||
try {
|
|
||||||
// Get registration options from your server. Here, we also receive the challenge.
|
|
||||||
const response = await fetch('/webauthn/passkey/registerBegin', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ username: this.username }),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the registration options are ok.
|
|
||||||
if (!response.ok) {
|
|
||||||
const msg = await response.json();
|
|
||||||
throw new Error(
|
|
||||||
'User already exists or failed to get registration options from server: ' +
|
|
||||||
msg,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the registration options to JSON.
|
|
||||||
const options = await response.json();
|
|
||||||
console.log('registration start', options);
|
|
||||||
|
|
||||||
// This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
|
|
||||||
// A new attestation is created. This also means a new public-private-key pair is created.
|
|
||||||
const attestationResponse = await startRegistration(options.publicKey);
|
|
||||||
|
|
||||||
console.log('Attempting to complete registration', attestationResponse);
|
|
||||||
// Send attestationResponse back to server for verification and storage.
|
|
||||||
const verificationResponse = await fetch(
|
|
||||||
'/webauthn/passkey/registerFinish',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(attestationResponse),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const msg = await verificationResponse.json();
|
|
||||||
if (verificationResponse.ok) {
|
|
||||||
this.error = undefined;
|
|
||||||
} else {
|
|
||||||
this.error = msg;
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
this.error = 'Error: ' + error.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
{{#each @notes as |note|}}
|
{{#each @notes as |note|}}
|
||||||
<Note @isInTimeline="true" @note={{note}}/>
|
<Note @isInTimeline="true" @note={{note}}/>
|
||||||
|
<hr class="timeline-separator">
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
27
frontend-reactive/app/components/util/map-edit.hbs
Normal file
27
frontend-reactive/app/components/util/map-edit.hbs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="{{@wrapper-classes}}">
|
||||||
|
<ul>
|
||||||
|
{{#each this.args.list as |element index|}}
|
||||||
|
<li>
|
||||||
|
<div class="string-array-element-wrapper">
|
||||||
|
<Input @type="text" @value={{element.key}} />
|
||||||
|
<Input @type="text" @value={{element.value}} />
|
||||||
|
<div
|
||||||
|
class="{{@remove-element-classes}}"
|
||||||
|
type="button"
|
||||||
|
{{on "click" (fn this.removeElement index)}}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
class="{{@add-element-classes}}"
|
||||||
|
type="button"
|
||||||
|
{{on "click" this.addElement}}
|
||||||
|
>
|
||||||
|
Add element
|
||||||
|
</div>
|
||||||
|
</div>
|
36
frontend-reactive/app/components/util/map-edit.ts
Normal file
36
frontend-reactive/app/components/util/map-edit.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import MutableArray from '@ember/array/mutable';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
|
||||||
|
export interface UtilMapEditSignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {
|
||||||
|
list: MutableArray<{ key: string; value: string }>;
|
||||||
|
prefix: string;
|
||||||
|
onNewElement: (index: number) => void;
|
||||||
|
onDeleteElement: (index: number) => void;
|
||||||
|
};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UtilMapEdit extends Component<UtilMapEditSignature> {
|
||||||
|
@action addElement() {
|
||||||
|
MutableArray.apply(this.args.list);
|
||||||
|
this.args.list.pushObject({ key: '', value: '' });
|
||||||
|
if (this.args.onNewElement)
|
||||||
|
this.args.onNewElement(this.args.list.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action removeElement(index: number) {
|
||||||
|
MutableArray.apply(this.args.list);
|
||||||
|
//let index = this.args.list.find((elem) => elem == content)
|
||||||
|
//let index = this.listCopy.findIndex((d) => d == content)
|
||||||
|
this.args.list.removeAt(index);
|
||||||
|
if (this.args.onDeleteElement) this.args.onDeleteElement(index);
|
||||||
|
}
|
||||||
|
}
|
13
frontend-reactive/app/components/util/multiselect.hbs
Normal file
13
frontend-reactive/app/components/util/multiselect.hbs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<div class={{@wrapper-class}}>
|
||||||
|
{{#each this.args.elements as |element|}}
|
||||||
|
<label class={{@label-class}}>
|
||||||
|
{{element.description}}
|
||||||
|
<Input
|
||||||
|
@type="checkbox"
|
||||||
|
name="{{element.name}}"
|
||||||
|
@checked={{element.checked}}
|
||||||
|
{{on "change" this.onChange}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
21
frontend-reactive/app/components/util/multiselect.ts
Normal file
21
frontend-reactive/app/components/util/multiselect.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
|
||||||
|
export interface UtilMultiselectSignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {
|
||||||
|
elements: Array<{ name: string; checked: boolean; description: string }>;
|
||||||
|
};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UtilMultiselect extends Component<UtilMultiselectSignature> {
|
||||||
|
@action onChange() {
|
||||||
|
console.log(this.args.elements);
|
||||||
|
}
|
||||||
|
}
|
12
frontend-reactive/app/components/util/one-of-array.hbs
Normal file
12
frontend-reactive/app/components/util/one-of-array.hbs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<div class="{{@wrapper-class}}">
|
||||||
|
{{#each @elements as |element|}}
|
||||||
|
<RadioButton
|
||||||
|
@value="{{element}}"
|
||||||
|
@groupValue={{@selected}}
|
||||||
|
@name={{@name}}
|
||||||
|
@required={{@required}}
|
||||||
|
>
|
||||||
|
{{element}}
|
||||||
|
</RadioButton>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
29
frontend-reactive/app/components/util/string-array.hbs
Normal file
29
frontend-reactive/app/components/util/string-array.hbs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<div class="{{@wrapper-classes}}">
|
||||||
|
<ul>
|
||||||
|
{{#each this.args.list as |element index|}}
|
||||||
|
<li>
|
||||||
|
<div class="string-array-element-wrapper">
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
@value={{element.value}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="{{@remove-element-classes}}"
|
||||||
|
type="button"
|
||||||
|
{{on "click" (fn this.removeElement index)}}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
class="{{@add-element-classes}}"
|
||||||
|
type="button"
|
||||||
|
{{on "click" this.addElement}}
|
||||||
|
>
|
||||||
|
Add element
|
||||||
|
</div>
|
||||||
|
</div>
|
56
frontend-reactive/app/components/util/string-array.ts
Normal file
56
frontend-reactive/app/components/util/string-array.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import MutableArray from '@ember/array/mutable';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
|
export interface UtilStringArraySignature {
|
||||||
|
// The arguments accepted by the component
|
||||||
|
Args: {
|
||||||
|
list: MutableArray<{ value: string }>;
|
||||||
|
prefix: string;
|
||||||
|
onNewElement: (index: number) => void;
|
||||||
|
onDeleteElement: (index: number) => void;
|
||||||
|
};
|
||||||
|
// Any blocks yielded by the component
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
// The element to which `...attributes` is applied in the component template
|
||||||
|
Element: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UtilStringArray extends Component<UtilStringArraySignature> {
|
||||||
|
@action addElement() {
|
||||||
|
MutableArray.apply(this.args.list);
|
||||||
|
this.args.list.pushObject({ value: '' });
|
||||||
|
if (this.args.onNewElement)
|
||||||
|
this.args.onNewElement(this.args.list.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action removeElement(index: number) {
|
||||||
|
MutableArray.apply(this.args.list);
|
||||||
|
//let index = this.args.list.find((elem) => elem == content)
|
||||||
|
//let index = this.listCopy.findIndex((d) => d == content)
|
||||||
|
this.args.list.removeAt(index);
|
||||||
|
if (this.args.onDeleteElement) this.args.onDeleteElement(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
transformArrayIntoUsable(arr: Array<string>): { [key: number]: string } {
|
||||||
|
const out: { [key: number]: string } = {};
|
||||||
|
const tmp = arr.map((elem: string, index: number) => {
|
||||||
|
out[index] = elem;
|
||||||
|
return elem;
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
countElemsInObj(obj: any): number {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const prop in obj) {
|
||||||
|
if (obj.hasOwnProperty(prop)) ++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
6
frontend-reactive/app/helpers/equals.ts
Normal file
6
frontend-reactive/app/helpers/equals.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { helper } from '@ember/component/helper';
|
||||||
|
|
||||||
|
export default helper(function equals(args) {
|
||||||
|
if (args.length != 2) return false;
|
||||||
|
return args[0] == args[1];
|
||||||
|
});
|
|
@ -8,4 +8,5 @@ export default class Router extends EmberRouter {
|
||||||
|
|
||||||
Router.map(function () {
|
Router.map(function () {
|
||||||
this.route('about');
|
this.route('about');
|
||||||
|
this.route('registerform');
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default class ApplicationRoute extends Route {
|
||||||
content: 'lorem ipsum',
|
content: 'lorem ipsum',
|
||||||
createdAt: Date.now() - 360000,
|
createdAt: Date.now() - 360000,
|
||||||
editedAt: Date.now() - 60000,
|
editedAt: Date.now() - 60000,
|
||||||
|
servertype: 'mastodon',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayname: 'Melody',
|
displayname: 'Melody',
|
||||||
|
@ -27,6 +28,7 @@ export default class ApplicationRoute extends Route {
|
||||||
content:
|
content:
|
||||||
'Grapple keel reef fathom haul wind bilge rat swing the lead belay line pink. Man-of-war mizzenmast killick lookout yo-ho-ho Sail ho gabion careen sutler stern. Draught wherry lookout schooner prow hail-shot spanker Letter of Marque lateen sail strike colors.\n\nLad heave to topgallant scallywag scuppers Spanish Main poop deck spike hulk broadside. Snow take a caulk hornswaggle gaff swab quarter lugger spanker bilge provost. Man-of-war measured fer yer chains lugger cable loaded to the gunwalls prow piracy snow doubloon furl.\n\nDead men tell no tales jib chase guns gunwalls Gold Road smartly nipperkin topsail bilge water Pirate Round. Gaff gunwalls bilged on her anchor bilge water scourge of the seven seas parley ho sheet chase guns squiffy. Scuppers fathom ho quarter gally heave to yardarm coxswain red ensign pink.',
|
'Grapple keel reef fathom haul wind bilge rat swing the lead belay line pink. Man-of-war mizzenmast killick lookout yo-ho-ho Sail ho gabion careen sutler stern. Draught wherry lookout schooner prow hail-shot spanker Letter of Marque lateen sail strike colors.\n\nLad heave to topgallant scallywag scuppers Spanish Main poop deck spike hulk broadside. Snow take a caulk hornswaggle gaff swab quarter lugger spanker bilge provost. Man-of-war measured fer yer chains lugger cable loaded to the gunwalls prow piracy snow doubloon furl.\n\nDead men tell no tales jib chase guns gunwalls Gold Road smartly nipperkin topsail bilge water Pirate Round. Gaff gunwalls bilged on her anchor bilge water scourge of the seven seas parley ho sheet chase guns squiffy. Scuppers fathom ho quarter gally heave to yardarm coxswain red ensign pink.',
|
||||||
createdAt: Date.now() - 3600,
|
createdAt: Date.now() - 3600,
|
||||||
|
servertype: 'linstrom',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayname: 'alice',
|
displayname: 'alice',
|
||||||
|
@ -35,6 +37,7 @@ export default class ApplicationRoute extends Route {
|
||||||
content: 'lorem ipsum',
|
content: 'lorem ipsum',
|
||||||
createdAt: Date.now() - 360000,
|
createdAt: Date.now() - 360000,
|
||||||
editedAt: Date.now() - 60000,
|
editedAt: Date.now() - 60000,
|
||||||
|
servertype: 'wafrn',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayname: 'Melody',
|
displayname: 'Melody',
|
||||||
|
@ -43,6 +46,7 @@ export default class ApplicationRoute extends Route {
|
||||||
content:
|
content:
|
||||||
'Grapple keel reef fathom haul wind bilge rat swing the lead belay line pink. Man-of-war mizzenmast killick lookout yo-ho-ho Sail ho gabion careen sutler stern. Draught wherry lookout schooner prow hail-shot spanker Letter of Marque lateen sail strike colors.\n\nLad heave to topgallant scallywag scuppers Spanish Main poop deck spike hulk broadside. Snow take a caulk hornswaggle gaff swab quarter lugger spanker bilge provost. Man-of-war measured fer yer chains lugger cable loaded to the gunwalls prow piracy snow doubloon furl.\n\nDead men tell no tales jib chase guns gunwalls Gold Road smartly nipperkin topsail bilge water Pirate Round. Gaff gunwalls bilged on her anchor bilge water scourge of the seven seas parley ho sheet chase guns squiffy. Scuppers fathom ho quarter gally heave to yardarm coxswain red ensign pink.',
|
'Grapple keel reef fathom haul wind bilge rat swing the lead belay line pink. Man-of-war mizzenmast killick lookout yo-ho-ho Sail ho gabion careen sutler stern. Draught wherry lookout schooner prow hail-shot spanker Letter of Marque lateen sail strike colors.\n\nLad heave to topgallant scallywag scuppers Spanish Main poop deck spike hulk broadside. Snow take a caulk hornswaggle gaff swab quarter lugger spanker bilge provost. Man-of-war measured fer yer chains lugger cable loaded to the gunwalls prow piracy snow doubloon furl.\n\nDead men tell no tales jib chase guns gunwalls Gold Road smartly nipperkin topsail bilge water Pirate Round. Gaff gunwalls bilged on her anchor bilge water scourge of the seven seas parley ho sheet chase guns squiffy. Scuppers fathom ho quarter gally heave to yardarm coxswain red ensign pink.',
|
||||||
createdAt: Date.now() - 3600,
|
createdAt: Date.now() - 3600,
|
||||||
|
servertype: 'unknown',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
9
frontend-reactive/app/routes/registerform.ts
Normal file
9
frontend-reactive/app/routes/registerform.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
|
export default class RegisterFormRoute extends Route {
|
||||||
|
async model() {
|
||||||
|
return {
|
||||||
|
list: [{ value: 'one' }, { value: 'two' }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
122
frontend-reactive/app/services/auth.ts
Normal file
122
frontend-reactive/app/services/auth.ts
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import Service from '@ember/service';
|
||||||
|
import {
|
||||||
|
startAuthentication,
|
||||||
|
startRegistration,
|
||||||
|
} from '@simplewebauthn/browser';
|
||||||
|
|
||||||
|
export default class AuthService extends Service {
|
||||||
|
async startLogin(username: string) {
|
||||||
|
// Get login options from your server. Here, we also receive the challenge.
|
||||||
|
const response = await fetch('/webauthn/passkey/loginBegin', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ username: username }),
|
||||||
|
});
|
||||||
|
// Check if the login options are ok.
|
||||||
|
if (!response.ok) {
|
||||||
|
const msg = await response.json();
|
||||||
|
throw new Error('Failed to get login options from server: ' + msg);
|
||||||
|
}
|
||||||
|
// Convert the login options to JSON.
|
||||||
|
const options = await response.json();
|
||||||
|
|
||||||
|
// This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
|
||||||
|
// A new assertionResponse is created. This also means that the challenge has been signed.
|
||||||
|
const assertionResponse = await startAuthentication(options.publicKey);
|
||||||
|
|
||||||
|
// Send assertionResponse back to server for verification.
|
||||||
|
const verificationResponse = await fetch('/webauthn/passkey/loginFinish', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(assertionResponse),
|
||||||
|
});
|
||||||
|
|
||||||
|
const msg = await verificationResponse.json();
|
||||||
|
if (verificationResponse.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Bad response code: ' +
|
||||||
|
verificationResponse.status +
|
||||||
|
'. Content: ' +
|
||||||
|
msg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startRegistration(username: string) {
|
||||||
|
// Get registration options from your server. Here, we also receive the challenge.
|
||||||
|
const response = await fetch('/webauthn/passkey/registerBegin', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ username: username }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the registration options are ok.
|
||||||
|
if (!response.ok) {
|
||||||
|
const msg = await response.json();
|
||||||
|
throw new Error(
|
||||||
|
'User already exists or failed to get registration options from server: ' +
|
||||||
|
msg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the registration options to JSON.
|
||||||
|
const options = await response.json();
|
||||||
|
console.log('registration start', options);
|
||||||
|
|
||||||
|
// This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
|
||||||
|
// A new attestation is created. This also means a new public-private-key pair is created.
|
||||||
|
const attestationResponse = await startRegistration(options.publicKey);
|
||||||
|
|
||||||
|
console.log('Attempting to complete registration', attestationResponse);
|
||||||
|
// Send attestationResponse back to server for verification and storage.
|
||||||
|
const verificationResponse = await fetch(
|
||||||
|
'/webauthn/passkey/registerFinish',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(attestationResponse),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const msg = await verificationResponse.json();
|
||||||
|
if (verificationResponse.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Bad response code: ' +
|
||||||
|
verificationResponse.status +
|
||||||
|
'. Content: ' +
|
||||||
|
msg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the client is currently logged in
|
||||||
|
// Happens via caling an endpoint that's only available if logged in but doesn't return anything other than "ok"
|
||||||
|
async getLoginState(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/test-auth');
|
||||||
|
return response.status >= 200 && response.status < 300;
|
||||||
|
} catch (error: any) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't remove this declaration: this is what enables TypeScript to resolve
|
||||||
|
// this service using `Owner.lookup('service:auth')`, as well
|
||||||
|
// as to check when you pass the service name as an argument to the decorator,
|
||||||
|
// like `@service('auth') declare altName: AuthService;`.
|
||||||
|
declare module '@ember/service' {
|
||||||
|
interface Registry {
|
||||||
|
auth: AuthService;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
import Service from '@ember/service'
|
|
||||||
|
|
||||||
export default class AuthenticationService extends Service {}
|
|
||||||
|
|
||||||
// Don't remove this declaration: this is what enables TypeScript to resolve
|
|
||||||
// this service using `Owner.lookup('service:authentication')`, as well
|
|
||||||
// as to check when you pass the service name as an argument to the decorator,
|
|
||||||
// like `@service('authentication') declare altName: AuthenticationService;`.
|
|
||||||
declare module '@ember/service' {
|
|
||||||
interface Registry {
|
|
||||||
authentication: AuthenticationService
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,11 @@
|
||||||
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
|
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
|
||||||
|
|
||||||
|
/* @import url("debug.css"); */
|
||||||
@import url("fonts.css");
|
@import url("fonts.css");
|
||||||
@import url("colors.css");
|
@import url("colors.css");
|
||||||
@import url("notes.css");
|
@import url("notes.css");
|
||||||
@import url("util.css");
|
@import url("util.css");
|
||||||
@import url("svgs.css");
|
@import url("svgs.css");
|
||||||
@import url("timeline.css");
|
@import url("timeline.css");
|
||||||
|
@import url("auth.css");
|
||||||
|
@import url("stringArray.css");
|
||||||
|
|
0
frontend-reactive/app/styles/auth.css
Normal file
0
frontend-reactive/app/styles/auth.css
Normal file
|
@ -140,4 +140,5 @@
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
3
frontend-reactive/app/styles/debug.css
Normal file
3
frontend-reactive/app/styles/debug.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
* {
|
||||||
|
border: red 1px dashed;
|
||||||
|
}
|
|
@ -6,12 +6,12 @@
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
|
||||||
/* align-items: center; */
|
/* align-items: center; */
|
||||||
border: 1px dashed red;
|
|
||||||
|
|
||||||
/* max-width: 50em; */
|
/* max-width: 50em; */
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
background-color: var(--background-100);
|
background-color: var(--background-100);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-user-header {
|
.note-user-header {
|
||||||
|
@ -23,7 +23,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-user-pfp {
|
.note-user-pfp {
|
||||||
border: 1px dashed red;
|
|
||||||
width: 3em;
|
width: 3em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
padding-bottom: 0.1em;
|
padding-bottom: 0.1em;
|
||||||
|
@ -44,7 +43,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-user-handle {
|
.note-user-handle {
|
||||||
/* font-size: 85%; */
|
font-size: 0.8em;
|
||||||
color: #555;
|
color: #555;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +54,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-timestamp {
|
.note-timestamp {
|
||||||
/* font-size: 0.8em; */
|
font-size: 0.8em;
|
||||||
margin: -0.1em;
|
margin: -0.1em;
|
||||||
color: var(--text-700);
|
color: var(--text-700);
|
||||||
}
|
}
|
||||||
|
@ -65,11 +64,9 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px dashed red;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-content-text {
|
.note-content-text {
|
||||||
border: 1px dashed red;
|
|
||||||
margin-top: -0.08em;
|
margin-top: -0.08em;
|
||||||
margin-bottom: -0.08em;
|
margin-bottom: -0.08em;
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
|
@ -80,7 +77,7 @@
|
||||||
margin-bottom: 0.3em;
|
margin-bottom: 0.3em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--secondary-300);
|
background-color: var(--secondary-300);
|
||||||
padding: 0.1em 0.3em 0.2em;
|
padding: 0.1em 0.3em;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #aaa;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +90,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 1px solid black;
|
border-top: 1px solid var(--text-300);
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
frontend-reactive/app/styles/stringArray.css
Normal file
4
frontend-reactive/app/styles/stringArray.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.string-array-element-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
|
@ -5,3 +5,7 @@
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-separator {
|
||||||
|
color: var(--text-400);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{page-title "FrontendReactive"}}
|
{{page-title "FrontendReactive"}}
|
||||||
|
|
||||||
{{outlet}}
|
{{outlet}}{{!----}}
|
||||||
<Timeline @notes={{@model.notes}} />
|
{{!--<Timeline @notes={{@model.notes}} />--}}
|
2
frontend-reactive/app/templates/registerform.hbs
Normal file
2
frontend-reactive/app/templates/registerform.hbs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{{page-title "RegisterForm"}}
|
||||||
|
<Auth::RegistrationForm @username="bob" />
|
Binary file not shown.
2219
frontend-reactive/package-lock.json
generated
2219
frontend-reactive/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -77,6 +77,7 @@
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
|
"ember-composable-helpers": "^5.0.0",
|
||||||
"ember-data": "~5.3.8",
|
"ember-data": "~5.3.8",
|
||||||
"ember-fetch": "^8.1.2",
|
"ember-fetch": "^8.1.2",
|
||||||
"ember-infinity": "^3.0.0",
|
"ember-infinity": "^3.0.0",
|
||||||
|
@ -86,7 +87,9 @@
|
||||||
"ember-moment": "^10.0.1",
|
"ember-moment": "^10.0.1",
|
||||||
"ember-page-title": "^8.2.3",
|
"ember-page-title": "^8.2.3",
|
||||||
"ember-qunit": "^8.1.0",
|
"ember-qunit": "^8.1.0",
|
||||||
|
"ember-radio-button": "^3.0.0-beta.1",
|
||||||
"ember-resolver": "^11.0.1",
|
"ember-resolver": "^11.0.1",
|
||||||
|
"ember-select-light": "^2.0.5",
|
||||||
"ember-simple-auth": "^6.1.0",
|
"ember-simple-auth": "^6.1.0",
|
||||||
"ember-source": "~5.11.0",
|
"ember-source": "~5.11.0",
|
||||||
"ember-template-lint": "^5.13.0",
|
"ember-template-lint": "^5.13.0",
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | auth/registration-form', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Auth::RegistrationForm />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Auth::RegistrationForm>
|
||||||
|
template block text
|
||||||
|
</Auth::RegistrationForm>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | note/formatter', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Note::Formatter />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Note::Formatter>
|
||||||
|
template block text
|
||||||
|
</Note::Formatter>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | note/formatter/akoma', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Note::Formatter::Akoma />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Note::Formatter::Akoma>
|
||||||
|
template block text
|
||||||
|
</Note::Formatter::Akoma>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | note/formatter/linstrom', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Note::Formatter::Linstrom />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Note::Formatter::Linstrom>
|
||||||
|
template block text
|
||||||
|
</Note::Formatter::Linstrom>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | note/formatter/mastodon', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Note::Formatter::Mastodon />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Note::Formatter::Mastodon>
|
||||||
|
template block text
|
||||||
|
</Note::Formatter::Mastodon>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | note/formatter/misskey', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Note::Formatter::Misskey />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Note::Formatter::Misskey>
|
||||||
|
template block text
|
||||||
|
</Note::Formatter::Misskey>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | note/formatter/wafrn', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Note::Formatter::Wafrn />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Note::Formatter::Wafrn>
|
||||||
|
template block text
|
||||||
|
</Note::Formatter::Wafrn>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | util/map-edit', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Util::MapEdit />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Util::MapEdit>
|
||||||
|
template block text
|
||||||
|
</Util::MapEdit>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | util/multiselect', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Util::Multiselect />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Util::Multiselect>
|
||||||
|
template block text
|
||||||
|
</Util::Multiselect>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | util/one-of-array', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Util::OneOfArray />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Util::OneOfArray>
|
||||||
|
template block text
|
||||||
|
</Util::OneOfArray>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | util/string-array', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
await render(hbs`<Util::StringArray />`);
|
||||||
|
|
||||||
|
assert.dom().hasText('');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
<Util::StringArray>
|
||||||
|
template block text
|
||||||
|
</Util::StringArray>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom().hasText('template block text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Helper | bindingArrayElement', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
// TODO: Replace this with your real tests.
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
this.set('inputValue', '1234');
|
||||||
|
|
||||||
|
await render(hbs`{{binding-array-element this.inputValue}}`);
|
||||||
|
|
||||||
|
assert.dom().hasText('1234');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Helper | countProperties', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
// TODO: Replace this with your real tests.
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
this.set('inputValue', '1234');
|
||||||
|
|
||||||
|
await render(hbs`{{count-properties this.inputValue}}`);
|
||||||
|
|
||||||
|
assert.dom().hasText('1234');
|
||||||
|
});
|
||||||
|
});
|
17
frontend-reactive/tests/integration/helpers/equals-test.ts
Normal file
17
frontend-reactive/tests/integration/helpers/equals-test.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Helper | equals', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
// TODO: Replace this with your real tests.
|
||||||
|
test('it renders', async function (assert) {
|
||||||
|
this.set('inputValue', '1234');
|
||||||
|
|
||||||
|
await render(hbs`{{equals this.inputValue}}`);
|
||||||
|
|
||||||
|
assert.dom().hasText('1234');
|
||||||
|
});
|
||||||
|
});
|
11
frontend-reactive/tests/unit/routes/register-form-test.ts
Normal file
11
frontend-reactive/tests/unit/routes/register-form-test.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
|
||||||
|
module('Unit | Route | registerForm', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
|
||||||
|
test('it exists', function (assert) {
|
||||||
|
const route = this.owner.lookup('route:register-form');
|
||||||
|
assert.ok(route);
|
||||||
|
});
|
||||||
|
});
|
12
frontend-reactive/tests/unit/services/auth-test.ts
Normal file
12
frontend-reactive/tests/unit/services/auth-test.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'frontend-reactive/tests/helpers';
|
||||||
|
|
||||||
|
module('Unit | Service | auth', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
|
||||||
|
// TODO: Replace this with your real tests.
|
||||||
|
test('it exists', function (assert) {
|
||||||
|
const service = this.owner.lookup('service:auth');
|
||||||
|
assert.ok(service);
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,4 +15,7 @@ const (
|
||||||
HttpErrIdDbFailure
|
HttpErrIdDbFailure
|
||||||
HttpErrIdNotAuthenticated
|
HttpErrIdNotAuthenticated
|
||||||
HttpErrIdJsonMarshalFail
|
HttpErrIdJsonMarshalFail
|
||||||
|
HttpErrIdBadRequest
|
||||||
|
HttpErrIdAlreadyExists
|
||||||
|
HttpErrIdNotFound
|
||||||
)
|
)
|
||||||
|
|
233
server/middlewareFixPasskeyPerms.go
Normal file
233
server/middlewareFixPasskeyPerms.go
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/hlog"
|
||||||
|
"gitlab.com/mstarongitlab/goutils/other"
|
||||||
|
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func forceCorrectPasskeyAuthFlowMiddleware(
|
||||||
|
handler http.Handler,
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := hlog.FromRequest(r)
|
||||||
|
// Don't fuck with the request if not intended for starting to register or login
|
||||||
|
if strings.HasSuffix(r.URL.Path, "loginFinish") {
|
||||||
|
log.Debug().Msg("Request to finish login method, doing nothing")
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "registerFinish") {
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
// Force unset session cookie here
|
||||||
|
w.Header().Del("Set-Cookie")
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "sid",
|
||||||
|
Value: "",
|
||||||
|
Path: "",
|
||||||
|
MaxAge: 0,
|
||||||
|
Expires: time.UnixMilli(0),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "loginBegin") {
|
||||||
|
fuckWithLoginRequest(w, r, handler)
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "registerBegin") {
|
||||||
|
fuckWithRegisterRequest(w, r, handler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuckWithRegisterRequest(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
nextHandler http.Handler,
|
||||||
|
) {
|
||||||
|
log := hlog.FromRequest(r)
|
||||||
|
log.Debug().Msg("Messing with register start request")
|
||||||
|
store := StorageFromRequest(w, r)
|
||||||
|
if store == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cookie, cookieErr := r.Cookie("sid")
|
||||||
|
var username struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
log.Debug().Bytes("body", body).Msg("Body of auth begin request")
|
||||||
|
err := json.Unmarshal(body, &username)
|
||||||
|
if err != nil {
|
||||||
|
other.HttpErr(w, HttpErrIdBadRequest, "Not a username json object", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cookieErr == nil {
|
||||||
|
// Already authenticated, overwrite username to logged in account's name
|
||||||
|
// Get session from cookie
|
||||||
|
log.Debug().Msg("Session token exists, force overwriting username of register request")
|
||||||
|
session, ok := store.GetSession(cookie.Value)
|
||||||
|
if !ok {
|
||||||
|
log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdDbFailure,
|
||||||
|
"Passkey session missing",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acc, err := store.FindAccountByPasskeyId(session.UserID)
|
||||||
|
// Assume account must exist if a session for it exists
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to get account from passkey id from session")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdDbFailure,
|
||||||
|
"Failed to get authenticated account",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Replace whatever username may be given with username of logged in account
|
||||||
|
newBody := strings.ReplaceAll(string(body), username.Username, acc.Username)
|
||||||
|
// Assign to request
|
||||||
|
r.Body = io.NopCloser(strings.NewReader(newBody))
|
||||||
|
r.ContentLength = int64(len(newBody))
|
||||||
|
// And pass on
|
||||||
|
nextHandler.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
// Not authenticated, ensure that no existing name is registered with
|
||||||
|
_, err = store.FindLocalAccountByUsername(username.Username)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
// No error while getting account means account exists, refuse access
|
||||||
|
log.Info().
|
||||||
|
Str("username", username.Username).
|
||||||
|
Msg("Account with same name already exists, preventing login")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdAlreadyExists,
|
||||||
|
"Account with that name already exists",
|
||||||
|
http.StatusBadRequest,
|
||||||
|
)
|
||||||
|
case storage.ErrEntryNotFound:
|
||||||
|
// Didn't find account with that name, give access
|
||||||
|
log.Debug().
|
||||||
|
Str("username", username.Username).
|
||||||
|
Msg("No account with this username exists yet, passing through")
|
||||||
|
// Copy original body since previous reader hit EOF
|
||||||
|
r.Body = io.NopCloser(strings.NewReader(string(body)))
|
||||||
|
r.ContentLength = int64(len(body))
|
||||||
|
nextHandler.ServeHTTP(w, r)
|
||||||
|
default:
|
||||||
|
// Some other error, log it and return appropriate message
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("username", username.Username).
|
||||||
|
Msg("Failed to check if account with username already exists")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdDbFailure,
|
||||||
|
"Failed to check if account with that name already exists",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuckWithLoginRequest(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
nextHandler http.Handler,
|
||||||
|
) {
|
||||||
|
log := hlog.FromRequest(r)
|
||||||
|
log.Debug().Msg("Messing with login start request")
|
||||||
|
store := StorageFromRequest(w, r)
|
||||||
|
if store == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cookie, cookieErr := r.Cookie("sid")
|
||||||
|
var username struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
// Force ignore cookie for now
|
||||||
|
_ = cookieErr
|
||||||
|
var err error = errors.New("placeholder")
|
||||||
|
if err == nil {
|
||||||
|
// Someone is logged in, overwrite username with logged in account's one
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
log.Debug().Bytes("body", body).Msg("Body of auth begin request")
|
||||||
|
err := json.Unmarshal(body, &username)
|
||||||
|
if err != nil {
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdBadRequest,
|
||||||
|
"Not a username json object",
|
||||||
|
http.StatusBadRequest,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session, ok := store.GetSession(cookie.Value)
|
||||||
|
if !ok {
|
||||||
|
log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdDbFailure,
|
||||||
|
"Passkey session missing",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acc, err := store.FindAccountByPasskeyId(session.UserID)
|
||||||
|
// Assume account must exist if a session for it exists
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to get account from passkey id from session")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdDbFailure,
|
||||||
|
"Failed to get authenticated account",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Replace whatever username may be given with username of logged in account
|
||||||
|
newBody := strings.ReplaceAll(string(body), username.Username, acc.Username)
|
||||||
|
// Assign to request
|
||||||
|
r.Body = io.NopCloser(strings.NewReader(newBody))
|
||||||
|
r.ContentLength = int64(len(newBody))
|
||||||
|
// And pass on
|
||||||
|
nextHandler.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
// No one logged in, check if user exists to prevent creating a bugged account
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
log.Debug().Bytes("body", body).Msg("Body of auth begin request")
|
||||||
|
err := json.Unmarshal(body, &username)
|
||||||
|
if err != nil {
|
||||||
|
other.HttpErr(w, HttpErrIdBadRequest, "Not a username json object", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = store.FindLocalAccountByUsername(username.Username)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
// All good, account exists, keep going
|
||||||
|
// Do nothing in this branch
|
||||||
|
case storage.ErrEntryNotFound:
|
||||||
|
// Account doesn't exist, catch it
|
||||||
|
other.HttpErr(w, HttpErrIdNotFound, "Username not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// catch db failures
|
||||||
|
log.Error().Err(err).Str("username", username.Username).Msg("Db failure while getting account")
|
||||||
|
other.HttpErr(w, HttpErrIdDbFailure, "Failed to check for account in db", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Restore body as new reader of the same content
|
||||||
|
r.Body = io.NopCloser(strings.NewReader(string(body)))
|
||||||
|
nextHandler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,13 @@ func NewServer(store *storage.Storage, pkey *passkey.Passkey, reactiveFS, static
|
||||||
|
|
||||||
func buildRootHandler(pkey *passkey.Passkey, reactiveFS, staticFS fs.FS) http.Handler {
|
func buildRootHandler(pkey *passkey.Passkey, reactiveFS, staticFS fs.FS) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
pkey.MountRoutes(mux, "/webauthn/")
|
mux.Handle(
|
||||||
|
"/webauthn/",
|
||||||
|
http.StripPrefix(
|
||||||
|
"/webauthn",
|
||||||
|
forceCorrectPasskeyAuthFlowMiddleware(buildPasskeyAuthRouter(pkey)),
|
||||||
|
),
|
||||||
|
)
|
||||||
mux.Handle("/", setupFrontendRouter(reactiveFS, staticFS))
|
mux.Handle("/", setupFrontendRouter(reactiveFS, staticFS))
|
||||||
mux.Handle("/pk/", http.StripPrefix("/pk", http.FileServer(http.Dir("pk-auth"))))
|
mux.Handle("/pk/", http.StripPrefix("/pk", http.FileServer(http.Dir("pk-auth"))))
|
||||||
mux.HandleFunc("/alive", isAliveHandler)
|
mux.HandleFunc("/alive", isAliveHandler)
|
||||||
|
@ -58,6 +64,12 @@ func buildRootHandler(pkey *passkey.Passkey, reactiveFS, staticFS fs.FS) http.Ha
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildPasskeyAuthRouter(pkey *passkey.Passkey) http.Handler {
|
||||||
|
router := http.NewServeMux()
|
||||||
|
pkey.MountRoutes(router, "/")
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) Start(addr string) error {
|
func (s *Server) Start(addr string) error {
|
||||||
log.Info().Str("addr", addr).Msg("Starting server")
|
log.Info().Str("addr", addr).Msg("Starting server")
|
||||||
return http.ListenAndServe(addr, s.router)
|
return http.ListenAndServe(addr, s.router)
|
||||||
|
|
Loading…
Reference in a new issue