Skip to content
This repository has been archived by the owner on Sep 27, 2024. It is now read-only.

Commit

Permalink
feat: use sessions to log in and out
Browse files Browse the repository at this point in the history
  • Loading branch information
RossComputerGuy committed May 2, 2024
1 parent 7214bbf commit ea7ccdc
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 32 deletions.
22 changes: 22 additions & 0 deletions lib/logic/route_args.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';

class AuthedRouteArguments {
const AuthedRouteArguments({
this.userName = null,
this.isSession = false,
});

final String? userName;
final bool isSession;

static AuthedRouteArguments? maybeOf(BuildContext context) {
final route = ModalRoute.of(context);
if (route == null) return null;
if (route.settings.arguments == null) return null;
return route.settings.arguments as AuthedRouteArguments;
}

static AuthedRouteArguments of(BuildContext context) {
return maybeOf(context) ?? AuthedRouteArguments();
}
}
20 changes: 9 additions & 11 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/material.dart';
import 'package:system_theme/system_theme.dart';

import 'logic/route_args.dart';
import 'logic/theme.dart' show buildThemeData;
import 'views/desktop.dart';
import 'views/lock.dart';
Expand Down Expand Up @@ -43,15 +44,6 @@ void main(List<String> argsList) async {
});
}

String? _getUserName(BuildContext context) {
final route = ModalRoute.of(context);
if (route == null) return null;
if (route.settings.arguments == null) return null;

final args = route.settings.arguments as Map<String, dynamic>;
return args['userName'];
}

class GenesisShellApp extends StatelessWidget {
const GenesisShellApp({
super.key,
Expand All @@ -78,8 +70,14 @@ class GenesisShellApp extends StatelessWidget {
),
themeMode: ThemeMode.dark,
routes: {
'/': (context) => DesktopView(userName: _getUserName(context)),
'/lock': (context) => LockView(userName: _getUserName(context)),
'/': (context) {
final args = AuthedRouteArguments.of(context);
return DesktopView(
userName: args.userName,
isSession: args.isSession,
);
},
'/lock': (context) => LockView(userName: AuthedRouteArguments.of(context).userName),
'/login': (_) => const LoginView(),
},
initialRoute: initLocked ? '/lock' : (displayManager ? '/login' : '/'),
Expand Down
9 changes: 9 additions & 0 deletions lib/views/desktop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ class DesktopView extends StatefulWidget {
this.desktopWallpaper = null,
this.mobileWallpaper = null,
this.userName = null,
this.isSession = false,
});

final String? wallpaper;
final String? desktopWallpaper;
final String? mobileWallpaper;
final String? userName;
final bool isSession;

@override
State<DesktopView> createState() => _DesktopViewState();
}

class _DesktopViewState extends State<DesktopView> {
static const authChannel = MethodChannel('com.expidusos.genesis.shell/auth');
static const sessionChannel = MethodChannel('com.expidusos.genesis.shell/session');

String? sessionName = null;
Expand All @@ -48,6 +51,12 @@ class _DesktopViewState extends State<DesktopView> {
sessionChannel.invokeMethod('close', sessionName).catchError((err) {
print(err);
});

if (widget.isSession && widget.userName != null) {
authChannel.invokeMethod('deauth', widget.userName).catchError((err) {
print(err);
});
}
}

@override
Expand Down
9 changes: 6 additions & 3 deletions lib/views/login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
import 'package:intl/intl.dart';

import '../logic/route_args.dart';
import '../logic/wallpaper.dart';

import '../widgets/account_profile.dart';
Expand Down Expand Up @@ -104,13 +105,15 @@ class _LoginViewState extends State<LoginView> {
),
const Spacer(),
LoginPrompt(
isSession: true,
onLogin: () {
final nav = Navigator.of(context);
nav.pushNamed(
'/',
arguments: {
'userName': _selectedUser!,
},
arguments: AuthedRouteArguments(
userName: _selectedUser!,
isSession: true,
),
);
},
),
Expand Down
3 changes: 3 additions & 0 deletions lib/widgets/login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ class LoginPrompt extends StatefulWidget {
const LoginPrompt({
super.key,
this.name,
this.isSession = false,
required this.onLogin,
});

final String? name;
final bool isSession;
final VoidCallback onLogin;

@override
Expand All @@ -32,6 +34,7 @@ class _LoginPromptState extends State<LoginPrompt> {

var args = <String, dynamic>{
'password': input,
'session': widget.isSession,
};

if (widget.name != null) {
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/power.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class PowerDialog extends StatelessWidget {
IconButton(
onPressed: () {
final nav = Navigator.of(context);
nav.popUntil(ModalRoute.withName(nav.widget.initialRoute ?? '/'));
nav.popUntil(ModalRoute.withName('/'));

if (nav.canPop()) nav.pop();
else SystemNavigator.pop();
Expand Down
4 changes: 2 additions & 2 deletions linux/application-priv.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#pragma once

#include "channels/auth.h"
#include "channels/session.h"

struct _GenesisShellApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
FlMethodChannel* outputs;
FlMethodChannel* account;
FlMethodChannel* auth;
AuthChannel auth;
SessionChannel session;
GtkWindow* win;
};
10 changes: 2 additions & 8 deletions linux/application.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "application.h"
#include "application-priv.h"
#include "channels/auth.h"
#include "channels/account.h"
#include "channels/outputs.h"

Expand Down Expand Up @@ -46,12 +45,7 @@ static void genesis_shell_application_activate(GApplication* application) {
fl_method_channel_set_method_call_handler(self->account, account_method_call_handler, self, nullptr);
}

{
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->auth = fl_method_channel_new(fl_engine_get_binary_messenger(fl_view_get_engine(view)), "com.expidusos.genesis.shell/auth", FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(self->auth, auth_method_call_handler, self, nullptr);
}

auth_channel_init(&self->auth, view);
session_channel_init(&self->session, view);
gtk_widget_grab_focus(GTK_WIDGET(view));
}
Expand Down Expand Up @@ -98,8 +92,8 @@ static void genesis_shell_application_dispose(GObject* object) {
GenesisShellApplication* self = GENESIS_SHELL_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
g_clear_object(&self->outputs);
g_clear_object(&self->auth);
g_clear_object(&self->account);
auth_channel_deinit(&self->auth);
session_channel_deinit(&self->session);
G_OBJECT_CLASS(genesis_shell_application_parent_class)->dispose(object);
}
Expand Down
68 changes: 64 additions & 4 deletions linux/channels/auth.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ static int conversation(int num_msg, const struct pam_message** msg, struct pam_
return PAM_SUCCESS;
}

void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) {
static void method_call_handler(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) {
AuthChannel* self = (AuthChannel*)user_data;

g_autoptr(FlMethodResponse) response = nullptr;
if (strcmp(fl_method_call_get_name(method_call), "auth") == 0) {
FlValue* args = fl_method_call_get_args(method_call);
Expand Down Expand Up @@ -62,16 +64,56 @@ void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_cal

r = pam_setcred(handle, PAM_REFRESH_CRED);
if (r != PAM_SUCCESS) {
fl_method_call_respond_error(method_call, "PAM", "pam_acct_mgmt failed", fl_value_new_string(pam_strerror(handle, r)), NULL);
fl_method_call_respond_error(method_call, "PAM", "pam_setcred failed", fl_value_new_string(pam_strerror(handle, r)), NULL);
pam_end(handle, r);
free(conv);
return;
}

pam_end(handle, r);
free(conv);
bool isSession = false;

if (fl_value_lookup_string(args, "session") != nullptr) {
isSession = fl_value_get_bool(fl_value_lookup_string(args, "session"));
}

if (isSession) {
if (g_hash_table_contains(self->sessions, username)) {
fl_method_call_respond_error(method_call, "Linux", "User session is already running", NULL, NULL);
pam_end(handle, r);
free(conv);
return;
}

r = pam_open_session(handle, PAM_SILENT);
if (r != PAM_SUCCESS) {
fl_method_call_respond_error(method_call, "PAM", "pam_open_session failed", fl_value_new_string(pam_strerror(handle, r)), NULL);
pam_end(handle, r);
free(conv);
return;
}

g_hash_table_insert(self->sessions, (gpointer)username, (gpointer)handle);
} else {
pam_end(handle, r);
free(conv);
}

response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} else if (strcmp(fl_method_call_get_name(method_call), "deauth") == 0) {
FlValue* args = fl_method_call_get_args(method_call);
const gchar* name = fl_value_get_string(args);

if (!g_hash_table_contains(self->sessions, name)) {
fl_method_call_respond_error(method_call, "Linux", "User does not have a session", NULL, NULL);
return;
}

g_hash_table_remove(self->sessions, name);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} else if (strcmp(fl_method_call_get_name(method_call), "hasSession") == 0) {
FlValue* args = fl_method_call_get_args(method_call);
const gchar* name = fl_value_get_string(args);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_bool(g_hash_table_contains(self->sessions, name))));
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
Expand All @@ -81,3 +123,21 @@ void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_cal
g_warning("Failed to send response: %s", error->message);
}
}

static void destory_session(pam_handle_t* handle) {
pam_close_session(handle, PAM_SILENT);
pam_end(handle, PAM_SUCCESS);
}

void auth_channel_init(AuthChannel* self, FlView* view) {
self->sessions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)destory_session);

g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->channel = fl_method_channel_new(fl_engine_get_binary_messenger(fl_view_get_engine(view)), "com.expidusos.genesis.shell/auth", FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(self->channel, method_call_handler, self, nullptr);
}

void auth_channel_deinit(AuthChannel* self) {
g_clear_object(&self->channel);
g_hash_table_unref(self->sessions);
}
8 changes: 7 additions & 1 deletion linux/channels/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@

#include "../application.h"

void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data);
typedef struct _AuthChannel {
GHashTable* sessions;
FlMethodChannel* channel;
} AuthChannel;

void auth_channel_init(AuthChannel* self, FlView* view);
void auth_channel_deinit(AuthChannel* self);
5 changes: 4 additions & 1 deletion linux/data/pam
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Genesis Shell login management

auth include login
auth substack login
account include login
password substack login
session include login
6 changes: 5 additions & 1 deletion nix/module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
];
};

security.pam.services.genesis-shell.text = lib.readFile ../linux/data/pam;
security.pam.services.genesis-shell = {
allowNullPassword = true;
startSession = true;
enableGnomeKeyring = lib.mkDefault config.services.gnome.gnome-keyring.enable;
};

nix.enable = false;

Expand Down

0 comments on commit ea7ccdc

Please sign in to comment.